From 66ffd65476ab09539e2cc0167edd079475e8dbd4 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Sat, 15 Nov 2025 20:17:23 +0000 Subject: [PATCH 01/11] Add deviceId to JSON info respose, to be used for the post-upgrade notfication system --- wled00/json.cpp | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/wled00/json.cpp b/wled00/json.cpp index a5ef7475..5661d0bd 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -1,5 +1,13 @@ #include "wled.h" +// Include SHA1 libraries for device ID generation +#ifdef ESP8266 + #include +#endif +#ifdef ESP32 + #include "mbedtls/sha1.h" +#endif + #define JSON_PATH_STATE 1 #define JSON_PATH_INFO 2 #define JSON_PATH_STATE_INFO 3 @@ -690,6 +698,38 @@ void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segme } } +// Generate a device ID based on SHA1 hash of MAC address +String getDeviceId() { + uint8_t mac[6]; + WiFi.macAddress(mac); + + #ifdef ESP8266 + // For ESP8266 we use the Hash.h library which is built into the ESP8266 Core + char macStr[18]; + sprintf(macStr, "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + String macString = String(macStr); + return sha1(macString); + #endif + + #ifdef ESP32 + // For ESP32 we use the mbedtls library which is built into the ESP32 core + unsigned char shaResult[20]; // SHA1 produces a hash of 20 bytes (which is 40 HEX characters) + mbedtls_sha1_context ctx; + mbedtls_sha1_init(&ctx); + mbedtls_sha1_starts_ret(&ctx); + mbedtls_sha1_update_ret(&ctx, mac, 6); + mbedtls_sha1_finish_ret(&ctx, shaResult); + mbedtls_sha1_free(&ctx); + + // Convert the Hash to a hexadecimal string + char buf[41]; + for (int i = 0; i < 20; i++) { + sprintf(&buf[i*2], "%02x", shaResult[i]); + } + return String(buf); + #endif +} + void serializeInfo(JsonObject root) { root[F("ver")] = versionString; @@ -697,6 +737,7 @@ void serializeInfo(JsonObject root) root[F("cn")] = F(WLED_CODENAME); root[F("release")] = releaseString; root[F("repo")] = repoString; + root[F("deviceId")] = getDeviceId(); JsonObject leds = root.createNestedObject(F("leds")); leds[F("count")] = strip.getLengthTotal(); From c649ec1d8c6d07cc7a4899ba1dbd5ba9cb6aac1f Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Mon, 17 Nov 2025 17:40:09 +0000 Subject: [PATCH 02/11] Update wled00/json.cpp Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- wled00/json.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/wled00/json.cpp b/wled00/json.cpp index 5661d0bd..e0be0580 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -700,6 +700,9 @@ void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segme // Generate a device ID based on SHA1 hash of MAC address String getDeviceId() { + static String cachedDeviceId = ""; + if (cachedDeviceId.length() > 0) return cachedDeviceId; + uint8_t mac[6]; WiFi.macAddress(mac); @@ -708,7 +711,8 @@ String getDeviceId() { char macStr[18]; sprintf(macStr, "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); String macString = String(macStr); - return sha1(macString); + cachedDeviceId = sha1(macString); + return cachedDeviceId; #endif #ifdef ESP32 @@ -726,8 +730,11 @@ String getDeviceId() { for (int i = 0; i < 20; i++) { sprintf(&buf[i*2], "%02x", shaResult[i]); } - return String(buf); + cachedDeviceId = String(buf); + return cachedDeviceId; #endif + + return String(""); } void serializeInfo(JsonObject root) From 4db86ebf7faebd8dfa4daf4347feed6270d0eb26 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Tue, 18 Nov 2025 05:35:49 +0000 Subject: [PATCH 03/11] Add salf and checksum --- wled00/json.cpp | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/wled00/json.cpp b/wled00/json.cpp index e0be0580..47bac547 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -698,7 +698,6 @@ void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segme } } -// Generate a device ID based on SHA1 hash of MAC address String getDeviceId() { static String cachedDeviceId = ""; if (cachedDeviceId.length() > 0) return cachedDeviceId; @@ -710,8 +709,10 @@ String getDeviceId() { // For ESP8266 we use the Hash.h library which is built into the ESP8266 Core char macStr[18]; sprintf(macStr, "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - String macString = String(macStr); - cachedDeviceId = sha1(macString); + String macString = String(macStr) + "WLED"; // Salt with "WLED" + String firstHash = sha1(macString); + String secondHash = sha1(firstHash); + cachedDeviceId = firstHash + secondHash.substring(38); // Concatenate first hash + last 2 chars of second return cachedDeviceId; #endif @@ -719,18 +720,39 @@ String getDeviceId() { // For ESP32 we use the mbedtls library which is built into the ESP32 core unsigned char shaResult[20]; // SHA1 produces a hash of 20 bytes (which is 40 HEX characters) mbedtls_sha1_context ctx; + + // First hash: MAC address + "WLED" salt mbedtls_sha1_init(&ctx); mbedtls_sha1_starts_ret(&ctx); mbedtls_sha1_update_ret(&ctx, mac, 6); + mbedtls_sha1_update_ret(&ctx, (const unsigned char*)"WLED", 4); mbedtls_sha1_finish_ret(&ctx, shaResult); mbedtls_sha1_free(&ctx); - // Convert the Hash to a hexadecimal string - char buf[41]; + // Convert first hash to hexadecimal string + char firstHash[41]; for (int i = 0; i < 20; i++) { - sprintf(&buf[i*2], "%02x", shaResult[i]); + sprintf(&firstHash[i*2], "%02x", shaResult[i]); } - cachedDeviceId = String(buf); + + // Second hash: SHA1 of the first hash + unsigned char shaResult2[20]; + mbedtls_sha1_init(&ctx); + mbedtls_sha1_starts_ret(&ctx); + mbedtls_sha1_update_ret(&ctx, (const unsigned char*)firstHash, 40); + mbedtls_sha1_finish_ret(&ctx, shaResult2); + mbedtls_sha1_free(&ctx); + + // Convert second hash to hexadecimal string + char secondHash[41]; + for (int i = 0; i < 20; i++) { + sprintf(&secondHash[i*2], "%02x", shaResult2[i]); + } + + // Return first hash + last 2 chars of second hash + char result[43]; + sprintf(result, "%s%c%c", firstHash, secondHash[38], secondHash[39]); + cachedDeviceId = String(result); return cachedDeviceId; #endif From 85b3c5d91bd7fa8d1f3e2a412cd33e484c31cc26 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Tue, 18 Nov 2025 05:53:12 +0000 Subject: [PATCH 04/11] refactor to use a common sha1 function --- wled00/fcn_declare.h | 2 ++ wled00/json.cpp | 67 -------------------------------------------- wled00/util.cpp | 55 +++++++++++++++++++++++++++++++++++- 3 files changed, 56 insertions(+), 68 deletions(-) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 01c2c2ec..2346ee45 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -401,6 +401,8 @@ uint8_t extractModeSlider(uint8_t mode, uint8_t slider, char *dest, uint8_t maxL int16_t extractModeDefaults(uint8_t mode, const char *segVar); void checkSettingsPIN(const char *pin); uint16_t crc16(const unsigned char* data_p, size_t length); +String computeSHA1(const String& input); +String getDeviceId(); uint16_t beatsin88_t(accum88 beats_per_minute_88, uint16_t lowest = 0, uint16_t highest = 65535, uint32_t timebase = 0, uint16_t phase_offset = 0); uint16_t beatsin16_t(accum88 beats_per_minute, uint16_t lowest = 0, uint16_t highest = 65535, uint32_t timebase = 0, uint16_t phase_offset = 0); uint8_t beatsin8_t(accum88 beats_per_minute, uint8_t lowest = 0, uint8_t highest = 255, uint32_t timebase = 0, uint8_t phase_offset = 0); diff --git a/wled00/json.cpp b/wled00/json.cpp index 47bac547..08468df5 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -1,12 +1,5 @@ #include "wled.h" -// Include SHA1 libraries for device ID generation -#ifdef ESP8266 - #include -#endif -#ifdef ESP32 - #include "mbedtls/sha1.h" -#endif #define JSON_PATH_STATE 1 #define JSON_PATH_INFO 2 @@ -698,66 +691,6 @@ void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segme } } -String getDeviceId() { - static String cachedDeviceId = ""; - if (cachedDeviceId.length() > 0) return cachedDeviceId; - - uint8_t mac[6]; - WiFi.macAddress(mac); - - #ifdef ESP8266 - // For ESP8266 we use the Hash.h library which is built into the ESP8266 Core - char macStr[18]; - sprintf(macStr, "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - String macString = String(macStr) + "WLED"; // Salt with "WLED" - String firstHash = sha1(macString); - String secondHash = sha1(firstHash); - cachedDeviceId = firstHash + secondHash.substring(38); // Concatenate first hash + last 2 chars of second - return cachedDeviceId; - #endif - - #ifdef ESP32 - // For ESP32 we use the mbedtls library which is built into the ESP32 core - unsigned char shaResult[20]; // SHA1 produces a hash of 20 bytes (which is 40 HEX characters) - mbedtls_sha1_context ctx; - - // First hash: MAC address + "WLED" salt - mbedtls_sha1_init(&ctx); - mbedtls_sha1_starts_ret(&ctx); - mbedtls_sha1_update_ret(&ctx, mac, 6); - mbedtls_sha1_update_ret(&ctx, (const unsigned char*)"WLED", 4); - mbedtls_sha1_finish_ret(&ctx, shaResult); - mbedtls_sha1_free(&ctx); - - // Convert first hash to hexadecimal string - char firstHash[41]; - for (int i = 0; i < 20; i++) { - sprintf(&firstHash[i*2], "%02x", shaResult[i]); - } - - // Second hash: SHA1 of the first hash - unsigned char shaResult2[20]; - mbedtls_sha1_init(&ctx); - mbedtls_sha1_starts_ret(&ctx); - mbedtls_sha1_update_ret(&ctx, (const unsigned char*)firstHash, 40); - mbedtls_sha1_finish_ret(&ctx, shaResult2); - mbedtls_sha1_free(&ctx); - - // Convert second hash to hexadecimal string - char secondHash[41]; - for (int i = 0; i < 20; i++) { - sprintf(&secondHash[i*2], "%02x", shaResult2[i]); - } - - // Return first hash + last 2 chars of second hash - char result[43]; - sprintf(result, "%s%c%c", firstHash, secondHash[38], secondHash[39]); - cachedDeviceId = String(result); - return cachedDeviceId; - #endif - - return String(""); -} void serializeInfo(JsonObject root) { diff --git a/wled00/util.cpp b/wled00/util.cpp index 09769c96..1f6d47df 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -3,6 +3,7 @@ #include "const.h" #ifdef ESP8266 #include "user_interface.h" // for bootloop detection +#include // for SHA1 on ESP8266 #else #include #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0) @@ -10,6 +11,7 @@ #elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(3, 3, 0) #include "soc/rtc.h" #endif +#include "mbedtls/sha1.h" // for SHA1 on ESP32 #endif @@ -1124,4 +1126,55 @@ uint8_t perlin8(uint16_t x, uint16_t y) { uint8_t perlin8(uint16_t x, uint16_t y, uint16_t z) { return (((perlin3D_raw((uint32_t)x << 8, (uint32_t)y << 8, (uint32_t)z << 8, true) * 2015) >> 10) + 33168) >> 8; //scale to 16 bit, offset, then scale to 8bit -} \ No newline at end of file +} + +// Platform-agnostic SHA1 computation from String input +String computeSHA1(const String& input) { + #ifdef ESP8266 + return sha1(input); // ESP8266 has built-in sha1() function + #else + // ESP32: Compute SHA1 hash using mbedtls + unsigned char shaResult[20]; // SHA1 produces 20 bytes + mbedtls_sha1_context ctx; + + mbedtls_sha1_init(&ctx); + mbedtls_sha1_starts_ret(&ctx); + mbedtls_sha1_update_ret(&ctx, (const unsigned char*)input.c_str(), input.length()); + mbedtls_sha1_finish_ret(&ctx, shaResult); + mbedtls_sha1_free(&ctx); + + // Convert to hexadecimal string + char hexString[41]; + for (int i = 0; i < 20; i++) { + sprintf(&hexString[i*2], "%02x", shaResult[i]); + } + hexString[40] = '\0'; + + return String(hexString); + #endif +} + +// Generate a device ID based on SHA1 hash of MAC address salted with "WLED" +// Returns: original SHA1 + last 2 chars of double-hashed SHA1 (42 chars total) +String getDeviceId() { + static String cachedDeviceId = ""; + if (cachedDeviceId.length() > 0) return cachedDeviceId; + + uint8_t mac[6]; + WiFi.macAddress(mac); + char macStr[18]; + sprintf(macStr, "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + + // First hash: MAC address + "WLED" salt + String macString = String(macStr) + "WLED"; + String firstHash = computeSHA1(macString); + + // Second hash: SHA1 of the first hash + String secondHash = computeSHA1(firstHash); + + // Concatenate first hash + last 2 chars of second hash + cachedDeviceId = firstHash + secondHash.substring(38); + + return cachedDeviceId; +} + From c1ce1d8aba136f7d5da447a9c3fc7a188e9a6de0 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Wed, 19 Nov 2025 23:11:31 +0000 Subject: [PATCH 05/11] salt using additional hardware details --- wled00/util.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/wled00/util.cpp b/wled00/util.cpp index 1f6d47df..019d24fa 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -1165,8 +1165,7 @@ String getDeviceId() { char macStr[18]; sprintf(macStr, "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - // First hash: MAC address + "WLED" salt - String macString = String(macStr) + "WLED"; + String macString = String(macStr) + "WLED" + ESP.getChipModel() + ESP.getChipRevision(); String firstHash = computeSHA1(macString); // Second hash: SHA1 of the first hash From a2935b87c21cebc1c687ccb41302fb66c1c58e95 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Wed, 19 Nov 2025 23:45:55 +0000 Subject: [PATCH 06/11] deviceString for 8266 --- wled00/util.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/wled00/util.cpp b/wled00/util.cpp index 019d24fa..984ee7fc 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -1165,8 +1165,12 @@ String getDeviceId() { char macStr[18]; sprintf(macStr, "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - String macString = String(macStr) + "WLED" + ESP.getChipModel() + ESP.getChipRevision(); - String firstHash = computeSHA1(macString); +#ifdef ESP8266 + String deviceString = String(macStr) + "WLED" + ESP.getCoreVersion(); +#else + String macString = String(macStr) + "WLED" + ESP. getChipModel() + ESP.getChipRevision(); +#endif + String firstHash = computeSHA1(deviceString); // Second hash: SHA1 of the first hash String secondHash = computeSHA1(firstHash); From 1860258deb36df16252c813baf13d25103dd2ddd Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Wed, 19 Nov 2025 23:48:30 +0000 Subject: [PATCH 07/11] deviceString for esp32 --- wled00/util.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/util.cpp b/wled00/util.cpp index 984ee7fc..0957022f 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -1168,7 +1168,7 @@ String getDeviceId() { #ifdef ESP8266 String deviceString = String(macStr) + "WLED" + ESP.getCoreVersion(); #else - String macString = String(macStr) + "WLED" + ESP. getChipModel() + ESP.getChipRevision(); + String deviceString = String(macStr) + "WLED" + ESP. getChipModel() + ESP.getChipRevision(); #endif String firstHash = computeSHA1(deviceString); From b90fbe6b1a6776f63977cdddd54898f572a623d7 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Wed, 19 Nov 2025 23:53:21 +0000 Subject: [PATCH 08/11] fix whitespace --- wled00/util.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/util.cpp b/wled00/util.cpp index 0957022f..9ded5805 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -1168,7 +1168,7 @@ String getDeviceId() { #ifdef ESP8266 String deviceString = String(macStr) + "WLED" + ESP.getCoreVersion(); #else - String deviceString = String(macStr) + "WLED" + ESP. getChipModel() + ESP.getChipRevision(); + String deviceString = String(macStr) + "WLED" + ESP.getChipModel() + ESP.getChipRevision(); #endif String firstHash = computeSHA1(deviceString); From a1aac452dea6189309c3c5b959bd4884751490ea Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Thu, 20 Nov 2025 00:24:24 +0000 Subject: [PATCH 09/11] use correct value for deviceString for 8266 and add comments --- wled00/util.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/wled00/util.cpp b/wled00/util.cpp index 9ded5805..0de0685f 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -1165,10 +1165,15 @@ String getDeviceId() { char macStr[18]; sprintf(macStr, "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + // The device string is deterministic as it needs to be consistent for the same device, even after a full flash erase + // MAC is salted with other consistent device info to avoid rainbow table attacks. + // If the MAC address is known by malicious actors, they could precompute SHA1 hashes to impersonate devices, + // but as WLED developers are just looking at statistics and not authenticating devices, this is acceptable. + // If the usage data was exfiltrated, you could not easily determine the MAC from the device ID without brute forcing SHA1 #ifdef ESP8266 - String deviceString = String(macStr) + "WLED" + ESP.getCoreVersion(); + String deviceString = String(macStr) + "WLED" + ESP.getChipId(); #else - String deviceString = String(macStr) + "WLED" + ESP.getChipModel() + ESP.getChipRevision(); + String deviceString = String(macStr) + "WLED" + ESP.getChipModel() + ESP.getChipRevision() + ESP.getEfuseMac(); #endif String firstHash = computeSHA1(deviceString); From 3dbcd79b3c9c3040954e5cbf2295074ae1a73d21 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Fri, 21 Nov 2025 08:17:38 +0000 Subject: [PATCH 10/11] Add efuse based data to salt --- wled00/util.cpp | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/wled00/util.cpp b/wled00/util.cpp index 0de0685f..f9993988 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -12,6 +12,7 @@ #include "soc/rtc.h" #endif #include "mbedtls/sha1.h" // for SHA1 on ESP32 +#include "esp_efuse.h" #endif @@ -1154,6 +1155,35 @@ String computeSHA1(const String& input) { #endif } +#ifdef ESP32 +static String dump_raw_block(esp_efuse_block_t block) +{ + const int WORDS = 8; // ESP32: 8×32-bit words per block i.e. 256bits + uint32_t buf[WORDS] = {0}; + + const esp_efuse_desc_t d = { + .efuse_block = block, + .bit_start = 0, + .bit_count = WORDS * 32 + }; + const esp_efuse_desc_t* field[2] = { &d, NULL }; + + esp_err_t err = esp_efuse_read_field_blob(field, buf, WORDS * 32); + if (err != ESP_OK) { + return ""; + } + + String result = ""; + for (const unsigned int i : buf) { + char line[32]; + sprintf(line, "0x%08X", i); + result += line; + } + return result; +} +#endif + + // Generate a device ID based on SHA1 hash of MAC address salted with "WLED" // Returns: original SHA1 + last 2 chars of double-hashed SHA1 (42 chars total) String getDeviceId() { @@ -1173,7 +1203,11 @@ String getDeviceId() { #ifdef ESP8266 String deviceString = String(macStr) + "WLED" + ESP.getChipId(); #else - String deviceString = String(macStr) + "WLED" + ESP.getChipModel() + ESP.getChipRevision() + ESP.getEfuseMac(); + String deviceString = String(macStr) + "WLED" + ESP.getChipModel() + ESP.getChipRevision(); + deviceString += dump_raw_block(EFUSE_BLK0); + deviceString += dump_raw_block(EFUSE_BLK1); + deviceString += dump_raw_block(EFUSE_BLK2); + deviceString += dump_raw_block(EFUSE_BLK3); #endif String firstHash = computeSHA1(deviceString); From 9b787e13d11efbae7c7893898d9b88f749f5817b Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Fri, 21 Nov 2025 08:22:22 +0000 Subject: [PATCH 11/11] swap to using ESP.getFlashChipId for the 8266 --- wled00/util.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/util.cpp b/wled00/util.cpp index f9993988..5f8c037d 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -1201,7 +1201,7 @@ String getDeviceId() { // but as WLED developers are just looking at statistics and not authenticating devices, this is acceptable. // If the usage data was exfiltrated, you could not easily determine the MAC from the device ID without brute forcing SHA1 #ifdef ESP8266 - String deviceString = String(macStr) + "WLED" + ESP.getChipId(); + String deviceString = String(macStr) + "WLED" + ESP.getFlashChipId(); #else String deviceString = String(macStr) + "WLED" + ESP.getChipModel() + ESP.getChipRevision(); deviceString += dump_raw_block(EFUSE_BLK0);