33#include " const.h"
44#ifdef ESP8266
55#include " user_interface.h" // for bootloop detection
6+ #include < Hash.h> // for SHA1 on ESP8266
67#else
78#include < Update.h>
89#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)
910 #include " esp32/rtc.h" // for bootloop detection
1011#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(3, 3, 0)
1112 #include " soc/rtc.h"
1213#endif
14+ #include " mbedtls/sha1.h" // for SHA1 on ESP32
15+ #include " esp_efuse.h"
1316#endif
1417
1518
@@ -1125,4 +1128,96 @@ uint8_t perlin8(uint16_t x, uint16_t y) {
11251128
11261129uint8_t perlin8 (uint16_t x, uint16_t y, uint16_t z) {
11271130 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
1128- }
1131+ }
1132+
1133+ // Platform-agnostic SHA1 computation from String input
1134+ String computeSHA1 (const String& input) {
1135+ #ifdef ESP8266
1136+ return sha1 (input); // ESP8266 has built-in sha1() function
1137+ #else
1138+ // ESP32: Compute SHA1 hash using mbedtls
1139+ unsigned char shaResult[20 ]; // SHA1 produces 20 bytes
1140+ mbedtls_sha1_context ctx;
1141+
1142+ mbedtls_sha1_init (&ctx);
1143+ mbedtls_sha1_starts_ret (&ctx);
1144+ mbedtls_sha1_update_ret (&ctx, (const unsigned char *)input.c_str (), input.length ());
1145+ mbedtls_sha1_finish_ret (&ctx, shaResult);
1146+ mbedtls_sha1_free (&ctx);
1147+
1148+ // Convert to hexadecimal string
1149+ char hexString[41 ];
1150+ for (int i = 0 ; i < 20 ; i++) {
1151+ sprintf (&hexString[i*2 ], " %02x" , shaResult[i]);
1152+ }
1153+ hexString[40 ] = ' \0 ' ;
1154+
1155+ return String (hexString);
1156+ #endif
1157+ }
1158+
1159+ #ifdef ESP32
1160+ static String dump_raw_block (esp_efuse_block_t block)
1161+ {
1162+ const int WORDS = 8 ; // ESP32: 8×32-bit words per block i.e. 256bits
1163+ uint32_t buf[WORDS] = {0 };
1164+
1165+ const esp_efuse_desc_t d = {
1166+ .efuse_block = block,
1167+ .bit_start = 0 ,
1168+ .bit_count = WORDS * 32
1169+ };
1170+ const esp_efuse_desc_t * field[2 ] = { &d, NULL };
1171+
1172+ esp_err_t err = esp_efuse_read_field_blob (field, buf, WORDS * 32 );
1173+ if (err != ESP_OK) {
1174+ return " " ;
1175+ }
1176+
1177+ String result = " " ;
1178+ for (const unsigned int i : buf) {
1179+ char line[32 ];
1180+ sprintf (line, " 0x%08X" , i);
1181+ result += line;
1182+ }
1183+ return result;
1184+ }
1185+ #endif
1186+
1187+
1188+ // Generate a device ID based on SHA1 hash of MAC address salted with "WLED"
1189+ // Returns: original SHA1 + last 2 chars of double-hashed SHA1 (42 chars total)
1190+ String getDeviceId () {
1191+ static String cachedDeviceId = " " ;
1192+ if (cachedDeviceId.length () > 0 ) return cachedDeviceId;
1193+
1194+ uint8_t mac[6 ];
1195+ WiFi.macAddress (mac);
1196+ char macStr[18 ];
1197+ sprintf (macStr, " %02x:%02x:%02x:%02x:%02x:%02x" , mac[0 ], mac[1 ], mac[2 ], mac[3 ], mac[4 ], mac[5 ]);
1198+
1199+ // The device string is deterministic as it needs to be consistent for the same device, even after a full flash erase
1200+ // MAC is salted with other consistent device info to avoid rainbow table attacks.
1201+ // If the MAC address is known by malicious actors, they could precompute SHA1 hashes to impersonate devices,
1202+ // but as WLED developers are just looking at statistics and not authenticating devices, this is acceptable.
1203+ // If the usage data was exfiltrated, you could not easily determine the MAC from the device ID without brute forcing SHA1
1204+ #ifdef ESP8266
1205+ String deviceString = String (macStr) + " WLED" + ESP.getFlashChipId ();
1206+ #else
1207+ String deviceString = String (macStr) + " WLED" + ESP.getChipModel () + ESP.getChipRevision ();
1208+ deviceString += dump_raw_block (EFUSE_BLK0);
1209+ deviceString += dump_raw_block (EFUSE_BLK1);
1210+ deviceString += dump_raw_block (EFUSE_BLK2);
1211+ deviceString += dump_raw_block (EFUSE_BLK3);
1212+ #endif
1213+ String firstHash = computeSHA1 (deviceString);
1214+
1215+ // Second hash: SHA1 of the first hash
1216+ String secondHash = computeSHA1 (firstHash);
1217+
1218+ // Concatenate first hash + last 2 chars of second hash
1219+ cachedDeviceId = firstHash + secondHash.substring (38 );
1220+
1221+ return cachedDeviceId;
1222+ }
1223+
0 commit comments