diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c9800dfe2b..ac306bfb1a8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,6 +54,7 @@ set(CORE_SRCS cores/esp32/freertos_stats.cpp cores/esp32/FunctionalInterrupt.cpp cores/esp32/HardwareSerial.cpp + cores/esp32/HashBuilder.cpp cores/esp32/HEXBuilder.cpp cores/esp32/IPAddress.cpp cores/esp32/libb64/cdecode.c @@ -62,7 +63,6 @@ set(CORE_SRCS cores/esp32/main.cpp cores/esp32/MD5Builder.cpp cores/esp32/Print.cpp - cores/esp32/SHA1Builder.cpp cores/esp32/stdlib_noniso.c cores/esp32/Stream.cpp cores/esp32/StreamString.cpp @@ -93,6 +93,7 @@ set(ARDUINO_ALL_LIBRARIES Ethernet FFat FS + Hash HTTPClient HTTPUpdate Insights @@ -154,6 +155,13 @@ set(ARDUINO_LIBRARY_FS_SRCS libraries/FS/src/FS.cpp libraries/FS/src/vfs_api.cpp) +set(ARDUINO_LIBRARY_Hash_SRCS + libraries/Hash/src/SHA1Builder.cpp + libraries/Hash/src/SHA2Builder.cpp + libraries/Hash/src/SHA3Builder.cpp + libraries/Hash/src/PBKDF2_HMACBuilder.cpp + ) + set(ARDUINO_LIBRARY_HTTPClient_SRCS libraries/HTTPClient/src/HTTPClient.cpp) set(ARDUINO_LIBRARY_HTTPUpdate_SRCS libraries/HTTPUpdate/src/HTTPUpdate.cpp) diff --git a/cores/esp32/HEXBuilder.cpp b/cores/esp32/HEXBuilder.cpp index 6154f58b384..4298ad65d6a 100644 --- a/cores/esp32/HEXBuilder.cpp +++ b/cores/esp32/HEXBuilder.cpp @@ -17,8 +17,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#include -#include +#include "HEXBuilder.h" static uint8_t hex_char_to_byte(uint8_t c) { return (c >= 'a' && c <= 'f') ? (c - ((uint8_t)'a' - 0xa)) diff --git a/cores/esp32/HEXBuilder.h b/cores/esp32/HEXBuilder.h index 0c35fbc1acc..c5b8a8a88f4 100644 --- a/cores/esp32/HEXBuilder.h +++ b/cores/esp32/HEXBuilder.h @@ -23,6 +23,8 @@ #include #include +// Basic hex/byte conversion class to be used by hash builders + class HEXBuilder { public: static size_t hex2bytes(unsigned char *out, size_t maxlen, String &in); diff --git a/cores/esp32/HashBuilder.cpp b/cores/esp32/HashBuilder.cpp new file mode 100644 index 00000000000..be3f67e2f78 --- /dev/null +++ b/cores/esp32/HashBuilder.cpp @@ -0,0 +1,38 @@ +// Copyright 2025 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "HashBuilder.h" + +void HashBuilder::add(const char *data) { + add((const uint8_t *)data, strlen(data)); +} + +void HashBuilder::add(String data) { + add(data.c_str()); +} + +void HashBuilder::addHexString(const char *data) { + size_t len = strlen(data); + uint8_t *tmp = (uint8_t *)malloc(len / 2); + if (tmp == NULL) { + return; + } + hex2bytes(tmp, len / 2, data); + add(tmp, len / 2); + free(tmp); +} + +void HashBuilder::addHexString(String data) { + addHexString(data.c_str()); +} diff --git a/cores/esp32/HashBuilder.h b/cores/esp32/HashBuilder.h index 77d1c71dbde..597c7b9a2a3 100644 --- a/cores/esp32/HashBuilder.h +++ b/cores/esp32/HashBuilder.h @@ -20,29 +20,26 @@ #include "HEXBuilder.h" +// Base class for hash builders + class HashBuilder : public HEXBuilder { public: virtual ~HashBuilder() {} virtual void begin() = 0; virtual void add(const uint8_t *data, size_t len) = 0; - virtual void add(const char *data) { - add((const uint8_t *)data, strlen(data)); - } - virtual void add(String data) { - add(data.c_str()); - } - - virtual void addHexString(const char *data) = 0; - virtual void addHexString(String data) { - addHexString(data.c_str()); - } + void add(const char *data); + void add(String data); + + void addHexString(const char *data); + void addHexString(String data); virtual bool addStream(Stream &stream, const size_t maxLen) = 0; virtual void calculate() = 0; virtual void getBytes(uint8_t *output) = 0; virtual void getChars(char *output) = 0; virtual String toString() = 0; + virtual size_t getHashSize() const = 0; }; #endif diff --git a/cores/esp32/MD5Builder.cpp b/cores/esp32/MD5Builder.cpp index cd8aa31b6cc..3c578491c13 100644 --- a/cores/esp32/MD5Builder.cpp +++ b/cores/esp32/MD5Builder.cpp @@ -17,9 +17,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#include -#include -#include +#include "HEXBuilder.h" +#include "MD5Builder.h" void MD5Builder::begin(void) { memset(_buf, 0x00, ESP_ROM_MD5_DIGEST_LEN); @@ -30,17 +29,6 @@ void MD5Builder::add(const uint8_t *data, size_t len) { esp_rom_md5_update(&_ctx, data, len); } -void MD5Builder::addHexString(const char *data) { - size_t len = strlen(data); - uint8_t *tmp = (uint8_t *)malloc(len / 2); - if (tmp == NULL) { - return; - } - hex2bytes(tmp, len / 2, data); - add(tmp, len / 2); - free(tmp); -} - bool MD5Builder::addStream(Stream &stream, const size_t maxLen) { const int buf_size = 512; int maxLengthLeft = maxLen; diff --git a/cores/esp32/MD5Builder.h b/cores/esp32/MD5Builder.h index 5728bd3bac0..7633670a610 100644 --- a/cores/esp32/MD5Builder.h +++ b/cores/esp32/MD5Builder.h @@ -35,19 +35,16 @@ class MD5Builder : public HashBuilder { uint8_t _buf[ESP_ROM_MD5_DIGEST_LEN]; public: - void begin(void) override; - using HashBuilder::add; - void add(const uint8_t *data, size_t len) override; - - using HashBuilder::addHexString; - void addHexString(const char *data) override; + void begin(void) override; + void add(const uint8_t *data, size_t len) override; bool addStream(Stream &stream, const size_t maxLen) override; void calculate(void) override; void getBytes(uint8_t *output) override; void getChars(char *output) override; String toString(void) override; + size_t getHashSize() const override { return ESP_ROM_MD5_DIGEST_LEN; } }; #endif diff --git a/libraries/ESP32/examples/Utilities/HEXBuilder/HEXBuilder.ino b/libraries/Hash/examples/HEX/HEX.ino similarity index 94% rename from libraries/ESP32/examples/Utilities/HEXBuilder/HEXBuilder.ino rename to libraries/Hash/examples/HEX/HEX.ino index f580a763b54..4ad78db3ad9 100644 --- a/libraries/ESP32/examples/Utilities/HEXBuilder/HEXBuilder.ino +++ b/libraries/Hash/examples/HEX/HEX.ino @@ -1,3 +1,9 @@ +/* + Usage example for the HEXBuilder class. + + This example shows how to convert a HEX string to a binary buffer and vice versa. +*/ + #include void setup() { diff --git a/libraries/ESP32/examples/Utilities/MD5Builder/MD5Builder.ino b/libraries/Hash/examples/MD5/MD5.ino similarity index 100% rename from libraries/ESP32/examples/Utilities/MD5Builder/MD5Builder.ino rename to libraries/Hash/examples/MD5/MD5.ino diff --git a/libraries/Hash/examples/PBKDF2_HMAC/PBKDF2_HMAC.ino b/libraries/Hash/examples/PBKDF2_HMAC/PBKDF2_HMAC.ino new file mode 100644 index 00000000000..fd4d4a8bdb8 --- /dev/null +++ b/libraries/Hash/examples/PBKDF2_HMAC/PBKDF2_HMAC.ino @@ -0,0 +1,178 @@ +/* + Usage example for the PBKDF2_HMACBuilder class. + + This example shows how to use the Hash library to hash data using the PBKDF2_HMACBuilder class. + PBKDF2_HMAC (Password-Based Key Derivation Function 2) is a key derivation function that uses a password and a salt to derive a key. + + The PBKDF2_HMACBuilder class takes for arguments: + - A HashBuilder object to use for the HMAC (SHA1Builder, SHA2Builder, SHA3Builder, etc.) + - A password string (default: empty) + - A salt string (default: empty) + - The number of iterations (default: 1000) +*/ + +#include +#include +#include + +void setup() { + Serial.begin(115200); + Serial.println("\n\nPBKDF2-HMAC Example"); + Serial.println("==================="); + + // Test 1: Basic PBKDF2-HMAC-SHA1 + Serial.println("\n1. PBKDF2-HMAC-SHA1 Test (1 iteration)"); + { + SHA1Builder sha1; + PBKDF2_HMACBuilder pbkdf2(&sha1, "password", "salt", 1); + + pbkdf2.begin(); + pbkdf2.calculate(); + + Serial.print("Password: "); + Serial.println("password"); + Serial.print("Salt: "); + Serial.println("salt"); + Serial.print("Iterations: "); + Serial.println(1); + Serial.print("Output (hex): "); + Serial.println(pbkdf2.toString()); + + // Expected: 0c60c80f961f0e71f3a9b524af6012062fe037a6 + String expected = "0c60c80f961f0e71f3a9b524af6012062fe037a6"; + String result = pbkdf2.toString(); + + if (result.equalsIgnoreCase(expected)) { + Serial.println("✓ PASS: Output matches expected value"); + } else { + Serial.println("✗ FAIL: Output does not match expected value"); + Serial.print("Expected: "); + Serial.println(expected); + Serial.print("Got: "); + Serial.println(result); + } + } + + // Test 2: PBKDF2-HMAC-SHA1 with more iterations + Serial.println("\n2. PBKDF2-HMAC-SHA1 Test (1000 iterations)"); + { + SHA1Builder sha1; + PBKDF2_HMACBuilder pbkdf2(&sha1); + + const char* password = "password"; + const char* salt = "salt"; + + pbkdf2.begin(); + pbkdf2.setPassword(password); + pbkdf2.setSalt(salt); + pbkdf2.setIterations(1000); + pbkdf2.calculate(); + + Serial.print("Password: "); + Serial.println(password); + Serial.print("Salt: "); + Serial.println(salt); + Serial.print("Iterations: "); + Serial.println(1000); + Serial.print("Output (hex): "); + Serial.println(pbkdf2.toString()); + + // Expected: 6e88be8bad7eae9d9e10aa061224034fed48d03f + String expected = "6e88be8bad7eae9d9e10aa061224034fed48d03f"; + String result = pbkdf2.toString(); + + if (result.equalsIgnoreCase(expected)) { + Serial.println("✓ PASS: Output matches expected value"); + } else { + Serial.println("✗ FAIL: Output does not match expected value"); + Serial.print("Expected: "); + Serial.println(expected); + Serial.print("Got: "); + Serial.println(result); + } + } + + // Test 3: PBKDF2-HMAC-SHA256 with different password and salt + Serial.println("\n3. PBKDF2-HMAC-SHA256 Test"); + { + SHA256Builder sha256; + PBKDF2_HMACBuilder pbkdf2(&sha256, "mySecretPassword", "randomSalt123", 100); + + pbkdf2.begin(); + pbkdf2.calculate(); + + Serial.print("Password: "); + Serial.println("mySecretPassword"); + Serial.print("Salt: "); + Serial.println("randomSalt123"); + Serial.print("Iterations: "); + Serial.println(100); + Serial.print("Output (hex): "); + Serial.println(pbkdf2.toString()); + + // Expected: 4ce309e56a37e0a4b9b84b98ed4a94e6c5cd5926cfd3baca3a6dea8c5d7903e8 + String expected = "4ce309e56a37e0a4b9b84b98ed4a94e6c5cd5926cfd3baca3a6dea8c5d7903e8"; + String result = pbkdf2.toString(); + + if (result.equalsIgnoreCase(expected)) { + Serial.println("✓ PASS: Output matches expected value"); + } else { + Serial.println("✗ FAIL: Output does not match expected value"); + Serial.print("Expected: "); + Serial.println(expected); + Serial.print("Got: "); + Serial.println(result); + } + } + + // Test 4: PBKDF2-HMAC-SHA1 with byte arrays + Serial.println("\n4. PBKDF2-HMAC-SHA1 Test (byte arrays)"); + { + SHA1Builder sha1; // or any other hash algorithm based on HashBuilder + PBKDF2_HMACBuilder pbkdf2(&sha1); + + uint8_t password[] = {0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64}; // "password" in bytes + uint8_t salt[] = {0x73, 0x61, 0x6c, 0x74}; // "salt" in bytes + + pbkdf2.begin(); + pbkdf2.setPassword(password, sizeof(password)); + pbkdf2.setSalt(salt, sizeof(salt)); + pbkdf2.setIterations(1); + pbkdf2.calculate(); + + Serial.print("Password (bytes): "); + for (int i = 0; i < sizeof(password); i++) { + Serial.print((char)password[i]); + } + Serial.println(); + Serial.print("Salt (bytes): "); + for (int i = 0; i < sizeof(salt); i++) { + Serial.print((char)salt[i]); + } + Serial.println(); + Serial.print("Iterations: "); + Serial.println(1); + Serial.print("Output (hex): "); + Serial.println(pbkdf2.toString()); + + // Expected: 0c60c80f961f0e71f3a9b524af6012062fe037a6 (same as test 1) + String expected = "0c60c80f961f0e71f3a9b524af6012062fe037a6"; + String result = pbkdf2.toString(); + + if (result.equalsIgnoreCase(expected)) { + Serial.println("✓ PASS: Output matches expected value"); + } else { + Serial.println("✗ FAIL: Output does not match expected value"); + Serial.print("Expected: "); + Serial.println(expected); + Serial.print("Got: "); + Serial.println(result); + } + } + + Serial.println("\nPBKDF2-HMAC tests completed!"); +} + +void loop() { + // Nothing to do in loop +} diff --git a/libraries/ESP32/examples/Utilities/SHA1Builder/SHA1Builder.ino b/libraries/Hash/examples/SHA1/SHA1.ino similarity index 100% rename from libraries/ESP32/examples/Utilities/SHA1Builder/SHA1Builder.ino rename to libraries/Hash/examples/SHA1/SHA1.ino diff --git a/libraries/Hash/examples/SHA2/SHA2.ino b/libraries/Hash/examples/SHA2/SHA2.ino new file mode 100644 index 00000000000..9de4f954ae4 --- /dev/null +++ b/libraries/Hash/examples/SHA2/SHA2.ino @@ -0,0 +1,94 @@ +/* + Usage example for the SHA2Builder class. + + This example shows how to use the SHA2 library to hash data using the SHA2Builder class. + SHA2 (Secure Hash Algorithm 2) provides different output sizes: SHA-224, SHA-256, SHA-384, and SHA-512. + + Available constructors: + - SHA224Builder(): 224-bit hash output + - SHA256Builder(): 256-bit hash output + - SHA384Builder(): 384-bit hash output + - SHA512Builder(): 512-bit hash output + - SHA2Builder(size_t hash_size): Generic class that can be used to create any SHA2 variant implemented +*/ + +#include + +// Expected hash values for validation +const char* EXPECTED_HELLO_WORLD_SHA256 = "a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e"; +const char* EXPECTED_HELLO_WORLD_SHA512 = "2c74fd17edafd80e8447b0d46741ee243b7eb74dd2149a0ab1b9246fb30382f27e853d8585719e0e67cbda0daa8f51671064615d645ae27acb15bfb1447f459b"; +const char* EXPECTED_TEST_MESSAGE_SHA224 = "155b033d801d4dd59b783d76ac3059053c00b2c28340a5a36a427a76"; +const char* EXPECTED_TEST_MESSAGE_SHA384 = "efd336618cbc96551936e5897e6af391d2480513ff8d4fc744e34462edb3111477d2b889c4d5e80e23b5f9d1b636fbd7"; + +// Validation function +bool validateHash(const String& calculated, const char* expected, const String& test_name) { + bool passed = (calculated == expected); + Serial.print(test_name); + Serial.print(": "); + Serial.println(passed ? "PASS" : "FAIL"); + Serial.print(" Expected: "); + Serial.println(expected); + Serial.print(" Got: "); + Serial.println(calculated); + return passed; +} + +void setup() { + Serial.begin(115200); + + Serial.println("\n\n\nStart."); + + // Using SHA2Builder class directly with different hash sizes + { + String test_data = "Hello World"; + Serial.println("Test data: " + test_data); + + // Create SHA-256 (default hash size) + SHA2Builder sha2_256; + sha2_256.begin(); + sha2_256.add(test_data); + sha2_256.calculate(); + String hash_256 = sha2_256.toString(); + validateHash(hash_256, EXPECTED_HELLO_WORLD_SHA256, "SHA-256 validation"); + + // Create SHA-512 + SHA2Builder sha2_512(SHA2_512_HASH_SIZE); + sha2_512.begin(); + sha2_512.add(test_data); + sha2_512.calculate(); + String hash_512 = sha2_512.toString(); + validateHash(hash_512, EXPECTED_HELLO_WORLD_SHA512, "SHA-512 validation"); + } + + // Example using SHA224Builder and SHA384Builder + // There are other constructors for other hash sizes available: + // - SHA224Builder() + // - SHA256Builder() + // - SHA384Builder() + // - SHA512Builder() + // - SHA2Builder(size_t hash_size) + { + String test_data = "Test message"; + Serial.println("Test data: " + test_data); + + // Create SHA-224 using specific constructor + SHA224Builder sha2_224; + sha2_224.begin(); + sha2_224.add(test_data); + sha2_224.calculate(); + String hash_224 = sha2_224.toString(); + validateHash(hash_224, EXPECTED_TEST_MESSAGE_SHA224, "SHA224Builder validation"); + + // Create SHA-384 using specific constructor + SHA384Builder sha2_384; + sha2_384.begin(); + sha2_384.add(test_data); + sha2_384.calculate(); + String hash_384 = sha2_384.toString(); + validateHash(hash_384, EXPECTED_TEST_MESSAGE_SHA384, "SHA384Builder validation"); + } + + Serial.println("Done."); +} + +void loop() {} diff --git a/libraries/Hash/examples/SHA3/SHA3.ino b/libraries/Hash/examples/SHA3/SHA3.ino new file mode 100644 index 00000000000..82e163f0840 --- /dev/null +++ b/libraries/Hash/examples/SHA3/SHA3.ino @@ -0,0 +1,94 @@ +/* + Usage example for the SHA3Builder class. + + This example shows how to use the SHA3 library to hash data using the SHA3Builder class. + SHA3 (Secure Hash Algorithm 3) provides different output sizes: SHA3-224, SHA3-256, SHA3-384, and SHA3-512. + + Available constructors: + - SHA3_224Builder(): 224-bit hash output + - SHA3_256Builder(): 256-bit hash output + - SHA3_384Builder(): 384-bit hash output + - SHA3_512Builder(): 512-bit hash output + - SHA3Builder(size_t hash_size): Generic class that can be used to create any SHA3 variant implemented +*/ + +#include + +// Expected hash values for validation +const char* EXPECTED_HELLO_WORLD_SHA3_256 = "e167f68d6563d75bb25f3aa49c29ef612d41352dc00606de7cbd630bb2665f51"; +const char* EXPECTED_HELLO_WORLD_SHA3_512 = "3d58a719c6866b0214f96b0a67b37e51a91e233ce0be126a08f35fdf4c043c6126f40139bfbc338d44eb2a03de9f7bb8eff0ac260b3629811e389a5fbee8a894"; +const char* EXPECTED_TEST_MESSAGE_SHA3_224 = "27af391bcb3b86f21b73c42c4abbde4791c395dc650243eede85de0c"; +const char* EXPECTED_TEST_MESSAGE_SHA3_384 = "adb18f6b164672c566950bfefa48c5a851d48ee184f249a19e723d753b7536fcd048c3443aff7ebe433fce63c81726ea"; + +// Validation function +bool validateHash(const String& calculated, const char* expected, const String& test_name) { + bool passed = (calculated == expected); + Serial.print(test_name); + Serial.print(": "); + Serial.println(passed ? "PASS" : "FAIL"); + Serial.print(" Expected: "); + Serial.println(expected); + Serial.print(" Got: "); + Serial.println(calculated); + return passed; +} + +void setup() { + Serial.begin(115200); + + Serial.println("\n\n\nStart."); + + // Using SHA3Builder class directly with different hash sizes + { + String test_data = "Hello World"; + Serial.println("Test data: " + test_data); + + // Create SHA3-256 (default hash size) + SHA3Builder sha3_256; + sha3_256.begin(); + sha3_256.add(test_data); + sha3_256.calculate(); + String hash_256 = sha3_256.toString(); + validateHash(hash_256, EXPECTED_HELLO_WORLD_SHA3_256, "SHA3-256 validation"); + + // Create SHA3-512 + SHA3Builder sha3_512(SHA3_512_HASH_SIZE); + sha3_512.begin(); + sha3_512.add(test_data); + sha3_512.calculate(); + String hash_512 = sha3_512.toString(); + validateHash(hash_512, EXPECTED_HELLO_WORLD_SHA3_512, "SHA3-512 validation"); + } + + // Example using SHA3_224Builder and SHA3_384Builder + // There are other constructors for other hash sizes available: + // - SHA3_224Builder() + // - SHA3_256Builder() + // - SHA3_384Builder() + // - SHA3_512Builder() + // - SHA3Builder(size_t hash_size) + { + String test_data = "Test message"; + Serial.println("Test data: " + test_data); + + // Create SHA3-224 using specific constructor + SHA3_224Builder sha3_224; + sha3_224.begin(); + sha3_224.add(test_data); + sha3_224.calculate(); + String hash_224 = sha3_224.toString(); + validateHash(hash_224, EXPECTED_TEST_MESSAGE_SHA3_224, "SHA3_224Builder validation"); + + // Create SHA3-384 using specific constructor + SHA3_384Builder sha3_384; + sha3_384.begin(); + sha3_384.add(test_data); + sha3_384.calculate(); + String hash_384 = sha3_384.toString(); + validateHash(hash_384, EXPECTED_TEST_MESSAGE_SHA3_384, "SHA3_384Builder validation"); + } + + Serial.println("Done."); +} + +void loop() {} diff --git a/libraries/Hash/examples/SHA3Stream/SHA3Stream.ino b/libraries/Hash/examples/SHA3Stream/SHA3Stream.ino new file mode 100644 index 00000000000..ca43cb58409 --- /dev/null +++ b/libraries/Hash/examples/SHA3Stream/SHA3Stream.ino @@ -0,0 +1,166 @@ +/* + Usage example for the SHA3Builder class with streams. + + This example shows how to use the SHA3 library to hash data from streams using the addStream method. + This is useful for hashing large files or data that comes from various stream sources like: + - File streams + - Network streams + - Memory streams + - Custom stream implementations + + Available constructors: + - SHA3_224Builder(): 224-bit hash output + - SHA3_256Builder(): 256-bit hash output + - SHA3_384Builder(): 384-bit hash output + - SHA3_512Builder(): 512-bit hash output + - SHA3Builder(size_t hash_size): Generic class that can be used to create any SHA3 variant implemented +*/ + +#include +#include + +// Expected hash values for validation +const char* EXPECTED_STREAM_TEST_SHA3_256 = "7094efc774885c7a785b408c5da86636cb8adc79156c0f162c6fd7e49f4c505e"; +const char* EXPECTED_MAX_SHA3_224_FULL = "ad0e69e04a7258d7cab4272a08ac69f8b43f4e45f9c49c9abb0628af"; +const char* EXPECTED_MAX_SHA3_224_10 = "9b55096e998cda6b96d3f2828c4ccda8c9964a1ad98989fb8b0fcd26"; +const char* EXPECTED_COMBINED_SHA3_256 = "4a32307fe03bf9f600c5d124419985fd4d42c1639e6a23ab044f107c3b95a189"; + +// Validation function +bool validateHash(const String& calculated, const char* expected, const String& test_name) { + bool passed = (calculated == expected); + Serial.print(test_name); + Serial.print(": "); + Serial.println(passed ? "PASS" : "FAIL"); + Serial.print(" Expected: "); + Serial.println(expected); + Serial.print(" Got: "); + Serial.println(calculated); + return passed; +} + +// Custom stream class for demonstration +class TestStream : public Stream { +private: + String data; + size_t position; + +public: + TestStream(String input_data) : data(input_data), position(0) {} + + virtual int available() override { + return data.length() - position; + } + + virtual int read() override { + if (position < data.length()) { + return data.charAt(position++); + } + return -1; + } + + virtual int peek() override { + if (position < data.length()) { + return data.charAt(position); + } + return -1; + } + + virtual size_t write(uint8_t) override { + return 0; // Read-only stream + } + + size_t length() { + return data.length(); + } + + void reset() { + position = 0; + } +}; + +void setup() { + Serial.begin(115200); + while (!Serial) { + delay(10); + } + + Serial.println("\n\nSHA3 Stream Example"); + Serial.println("==================="); + + // Example 1: Using addStream with a custom stream + { + Serial.println("\n1. Hashing data from a custom stream:"); + + const char* test_data = "This is a test message for streaming hash calculation. " + "It contains multiple sentences to demonstrate how the " + "addStream method processes data in chunks."; + + TestStream stream(test_data); + + SHA3_256Builder sha3_256; + sha3_256.begin(); + + // Hash the entire stream + // First argument is the stream, second argument is the maximum length to be read from the stream + sha3_256.addStream(stream, stream.length()); // Reading the entire stream + sha3_256.calculate(); + String hash_256 = sha3_256.toString(); + validateHash(hash_256, EXPECTED_STREAM_TEST_SHA3_256, "Stream test validation"); + } + + // Example 2: Using addStream with different maximum lengths + { + Serial.println("\n2. Comparing different maximum lengths with streams:"); + + const char* test_data = "Streaming hash test with different maximum lengths"; + TestStream stream(test_data); + + // SHA3-224 with a hardcoded maximum length + stream.reset(); + SHA3_224Builder sha3_224_10; + sha3_224_10.begin(); + sha3_224_10.addStream(stream, 10); // Passing a hardcoded maximum length to be read from the stream + sha3_224_10.calculate(); + String hash_224_10 = sha3_224_10.toString(); + validateHash(hash_224_10, EXPECTED_MAX_SHA3_224_10, "SHA3-224 with 10 bytes"); + + // SHA3-224 with the full stream + stream.reset(); + SHA3_224Builder sha3_224_full; + sha3_224_full.begin(); + sha3_224_full.addStream(stream, stream.length()); // Reading the entire stream + sha3_224_full.calculate(); + String hash_224_full = sha3_224_full.toString(); + validateHash(hash_224_full, EXPECTED_MAX_SHA3_224_FULL, "SHA3-224 with full stream"); + } + + // Example 3: Combining add() and addStream() + { + Serial.println("\n3. Combining add() and addStream():"); + + const char* stream_data = "Additional data from stream"; + TestStream stream(stream_data); + + SHA3_256Builder sha3_256; + sha3_256.begin(); + + // Add some data directly + sha3_256.add("Initial data: "); + + // Add data from stream + sha3_256.addStream(stream, stream.length()); + + // Add more data directly + sha3_256.add(" : Final data"); + + sha3_256.calculate(); + String hash_256 = sha3_256.toString(); + validateHash(hash_256, EXPECTED_COMBINED_SHA3_256, "Combined data validation"); + } + + Serial.println("\nStream example completed!"); +} + +void loop() { + // Nothing to do in loop +} diff --git a/libraries/Hash/keywords.txt b/libraries/Hash/keywords.txt new file mode 100644 index 00000000000..d553b7b428e --- /dev/null +++ b/libraries/Hash/keywords.txt @@ -0,0 +1,51 @@ +####################################### +# Syntax Coloring Map For Hash +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +HashBuilder KEYWORD1 +HEXBuilder KEYWORD1 +MD5Builder KEYWORD1 +SHA1Builder KEYWORD1 +SHA2Builder KEYWORD1 +SHA224Builder KEYWORD1 +SHA256Builder KEYWORD1 +SHA384Builder KEYWORD1 +SHA512Builder KEYWORD1 +SHA3Builder KEYWORD1 +PBKDF2_HMACBuilder KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +begin KEYWORD2 +add KEYWORD2 +addHexString KEYWORD2 +addStream KEYWORD2 +calculate KEYWORD2 +getBytes KEYWORD2 +getChars KEYWORD2 +toString KEYWORD2 +hex2bytes KEYWORD2 +bytes2hex KEYWORD2 +getHashSize KEYWORD2 +setPassword KEYWORD2 +setSalt KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### + +SHA1_HASH_SIZE LITERAL1 +SHA2_224_HASH_SIZE LITERAL1 +SHA2_256_HASH_SIZE LITERAL1 +SHA2_384_HASH_SIZE LITERAL1 +SHA2_512_HASH_SIZE LITERAL1 +SHA3_224_HASH_SIZE LITERAL1 +SHA3_256_HASH_SIZE LITERAL1 +SHA3_384_HASH_SIZE LITERAL1 +SHA3_512_HASH_SIZE LITERAL1 diff --git a/libraries/Hash/library.properties b/libraries/Hash/library.properties new file mode 100644 index 00000000000..5db4df17c2d --- /dev/null +++ b/libraries/Hash/library.properties @@ -0,0 +1,9 @@ +name=Hash +version=3.3.0 +author=lucasssvaz +maintainer=lucasssvaz +sentence=Bundle of hashing functions for the ESP32 +paragraph=This library provides a set of hashing functions to be used in the sketches +category=Security +url= +architectures=esp32 diff --git a/libraries/Hash/src/PBKDF2_HMACBuilder.cpp b/libraries/Hash/src/PBKDF2_HMACBuilder.cpp new file mode 100644 index 00000000000..5dcec96f87e --- /dev/null +++ b/libraries/Hash/src/PBKDF2_HMACBuilder.cpp @@ -0,0 +1,258 @@ +// Copyright 2025 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include "PBKDF2_HMACBuilder.h" + +// Block size for HMAC (64 bytes for SHA-1, SHA-256, SHA-512) +#define HMAC_BLOCK_SIZE 64 + +PBKDF2_HMACBuilder::PBKDF2_HMACBuilder(HashBuilder* hash, String password, String salt, uint32_t iterations) { + this->hashBuilder = hash; + this->hashSize = hashBuilder->getHashSize(); + this->iterations = iterations; + + // Initialize pointers + this->password = nullptr; + this->salt = nullptr; + this->passwordLen = 0; + this->saltLen = 0; + this->derivedKey = nullptr; + this->derivedKeyLen = 0; + this->calculated = false; + + if (password.length() > 0) { + setPassword(password); + } + + if (salt.length() > 0) { + setSalt(salt); + } +} + +PBKDF2_HMACBuilder::~PBKDF2_HMACBuilder() { + clearData(); +} + +void PBKDF2_HMACBuilder::clearData() { + if (derivedKey != nullptr) { + delete[] derivedKey; + derivedKey = nullptr; + } + derivedKeyLen = 0; + calculated = false; +} + +void PBKDF2_HMACBuilder::hmac(const uint8_t* key, size_t keyLen, const uint8_t* data, size_t dataLen, uint8_t* output) { + uint8_t keyPad[HMAC_BLOCK_SIZE]; + uint8_t outerPad[HMAC_BLOCK_SIZE]; + uint8_t innerHash[64]; // Large enough for any hash + + // Prepare key + if (keyLen > HMAC_BLOCK_SIZE) { + // Key is longer than block size, hash it + hashBuilder->begin(); + hashBuilder->add(key, keyLen); + hashBuilder->calculate(); + hashBuilder->getBytes(keyPad); + keyLen = hashSize; + } else { + // Copy key to keyPad + memcpy(keyPad, key, keyLen); + } + + // Pad key with zeros if necessary + if (keyLen < HMAC_BLOCK_SIZE) { + memset(keyPad + keyLen, 0, HMAC_BLOCK_SIZE - keyLen); + } + + // Create outer and inner pads + for (int i = 0; i < HMAC_BLOCK_SIZE; i++) { + outerPad[i] = keyPad[i] ^ 0x5c; + keyPad[i] = keyPad[i] ^ 0x36; + } + + // Inner hash: H(K XOR ipad, text) + hashBuilder->begin(); + hashBuilder->add(keyPad, HMAC_BLOCK_SIZE); + hashBuilder->add(data, dataLen); + hashBuilder->calculate(); + hashBuilder->getBytes(innerHash); + + // Outer hash: H(K XOR opad, inner_hash) + hashBuilder->begin(); + hashBuilder->add(outerPad, HMAC_BLOCK_SIZE); + hashBuilder->add(innerHash, hashSize); + hashBuilder->calculate(); + hashBuilder->getBytes(output); +} + +// HashBuilder interface methods +void PBKDF2_HMACBuilder::begin() { + clearData(); +} + +void PBKDF2_HMACBuilder::add(const uint8_t *data, size_t len) { + log_w("PBKDF2_HMACBuilder::add sets only the password. Use setPassword() and setSalt() instead."); + setPassword(data, len); +} + +bool PBKDF2_HMACBuilder::addStream(Stream &stream, const size_t maxLen) { + log_e("PBKDF2_HMACBuilder does not support addStream. Use setPassword() and setSalt() instead."); + return false; +} + +void PBKDF2_HMACBuilder::calculate() { + if (password == nullptr || salt == nullptr) { + log_e("Error: Password or salt not set."); + return; + } + + // Set default output size to hash size if not specified + if (derivedKeyLen == 0) { + derivedKeyLen = hashSize; + } + + // Allocate output buffer + if (derivedKey != nullptr) { + delete[] derivedKey; + } + derivedKey = new uint8_t[derivedKeyLen]; + + // Perform PBKDF2-HMAC + pbkdf2_hmac(password, passwordLen, salt, saltLen, iterations, derivedKey, derivedKeyLen); + calculated = true; +} + +void PBKDF2_HMACBuilder::getBytes(uint8_t *output) { + if (!calculated || derivedKey == nullptr) { + log_e("Error: PBKDF2-HMAC not calculated or no output buffer provided."); + return; + } + memcpy(output, derivedKey, derivedKeyLen); +} + +void PBKDF2_HMACBuilder::getChars(char *output) { + if (!calculated || derivedKey == nullptr) { + log_e("Error: PBKDF2-HMAC not calculated or no output buffer provided."); + return; + } + for (size_t i = 0; i < derivedKeyLen; i++) { + output[i] = (char)derivedKey[i]; + } +} + +String PBKDF2_HMACBuilder::toString() { + if (!calculated || derivedKey == nullptr) { + log_e("Error: PBKDF2-HMAC not calculated or no output buffer provided."); + return ""; + } + + String result = ""; + for (size_t i = 0; i < derivedKeyLen; i++) { + if (derivedKey[i] < 0x10) { + result += "0"; + } + result += String(derivedKey[i], HEX); + } + return result; +} + +// PBKDF2 specific methods +void PBKDF2_HMACBuilder::setPassword(const uint8_t* password, size_t len) { + if (this->password != nullptr) { + delete[] this->password; + } + this->password = new uint8_t[len]; + memcpy(this->password, password, len); + this->passwordLen = len; + calculated = false; +} + +void PBKDF2_HMACBuilder::setPassword(const char* password) { + setPassword((const uint8_t*)password, strlen(password)); +} + +void PBKDF2_HMACBuilder::setPassword(String password) { + setPassword((const uint8_t*)password.c_str(), password.length()); +} + +void PBKDF2_HMACBuilder::setSalt(const uint8_t* salt, size_t len) { + if (this->salt != nullptr) { + delete[] this->salt; + } + this->salt = new uint8_t[len]; + memcpy(this->salt, salt, len); + this->saltLen = len; + calculated = false; +} + +void PBKDF2_HMACBuilder::setSalt(const char* salt) { + setSalt((const uint8_t*)salt, strlen(salt)); +} + +void PBKDF2_HMACBuilder::setSalt(String salt) { + setSalt((const uint8_t*)salt.c_str(), salt.length()); +} + +void PBKDF2_HMACBuilder::setIterations(uint32_t iterations) { + this->iterations = iterations; +} + +void PBKDF2_HMACBuilder::setHashAlgorithm(HashBuilder* hash) { + // Set the hash algorithm to use for the HMAC + // Note: We don't delete hashBuilder here as it might be owned by the caller + // The caller is responsible for managing the hashBuilder lifetime + hashBuilder = hash; + hashSize = hashBuilder->getHashSize(); +} + +void PBKDF2_HMACBuilder::pbkdf2_hmac(const uint8_t* password, size_t passwordLen, + const uint8_t* salt, size_t saltLen, + uint32_t iterations, uint8_t* output, size_t outputLen) { + uint8_t u1[64]; // Large enough for any hash + uint8_t u2[64]; + uint8_t saltWithBlock[256]; // Salt + block number + uint8_t block[64]; + + size_t blocks = (outputLen + hashSize - 1) / hashSize; + + for (size_t i = 1; i <= blocks; i++) { + // Prepare salt || INT(i) + memcpy(saltWithBlock, salt, saltLen); + saltWithBlock[saltLen] = (i >> 24) & 0xFF; + saltWithBlock[saltLen + 1] = (i >> 16) & 0xFF; + saltWithBlock[saltLen + 2] = (i >> 8) & 0xFF; + saltWithBlock[saltLen + 3] = i & 0xFF; + + // U1 = HMAC(password, salt || INT(i)) + hmac(password, passwordLen, saltWithBlock, saltLen + 4, u1); + memcpy(block, u1, hashSize); + + // U2 = HMAC(password, U1) + for (uint32_t j = 1; j < iterations; j++) { + hmac(password, passwordLen, u1, hashSize, u2); + memcpy(u1, u2, hashSize); + + // XOR with previous result + for (size_t k = 0; k < hashSize; k++) { + block[k] ^= u1[k]; + } + } + + // Copy block to output + size_t copyLen = (i == blocks) ? (outputLen - (i - 1) * hashSize) : hashSize; + memcpy(output + (i - 1) * hashSize, block, copyLen); + } +} diff --git a/libraries/Hash/src/PBKDF2_HMACBuilder.h b/libraries/Hash/src/PBKDF2_HMACBuilder.h new file mode 100644 index 00000000000..e02fc67c8bf --- /dev/null +++ b/libraries/Hash/src/PBKDF2_HMACBuilder.h @@ -0,0 +1,73 @@ +// Copyright 2025 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PBKDF2_HMACBuilder_h +#define PBKDF2_HMACBuilder_h + +#include +#include +#include "HashBuilder.h" + +class PBKDF2_HMACBuilder : public HashBuilder { +private: + HashBuilder* hashBuilder; + size_t hashSize; + uint32_t iterations; + + // Password and salt storage + uint8_t* password; + size_t passwordLen; + uint8_t* salt; + size_t saltLen; + + // Output storage + uint8_t* derivedKey; + size_t derivedKeyLen; + bool calculated; + + void hmac(const uint8_t* key, size_t keyLen, const uint8_t* data, size_t dataLen, uint8_t* output); + void pbkdf2_hmac(const uint8_t* password, size_t passwordLen, + const uint8_t* salt, size_t saltLen, + uint32_t iterations, uint8_t* output, size_t outputLen); + void clearData(); + +public: + using HashBuilder::add; + + // Constructor takes a hash builder instance + PBKDF2_HMACBuilder(HashBuilder* hash, String password = "", String salt = "", uint32_t iterations = 10000); + ~PBKDF2_HMACBuilder(); + + // Standard HashBuilder interface + void begin() override; + void add(const uint8_t *data, size_t len) override; + bool addStream(Stream &stream, const size_t maxLen) override; + void calculate() override; + void getBytes(uint8_t *output) override; + void getChars(char *output) override; + String toString() override; + size_t getHashSize() const override { return derivedKeyLen; } + + // PBKDF2 specific methods + void setPassword(const uint8_t* password, size_t len); + void setPassword(const char* password); + void setPassword(String password); + void setSalt(const uint8_t* salt, size_t len); + void setSalt(const char* salt); + void setSalt(String salt); + void setIterations(uint32_t iterations); + void setHashAlgorithm(HashBuilder* hash); +}; + +#endif diff --git a/cores/esp32/SHA1Builder.cpp b/libraries/Hash/src/SHA1Builder.cpp similarity index 88% rename from cores/esp32/SHA1Builder.cpp rename to libraries/Hash/src/SHA1Builder.cpp index 6bbe3ca83e0..bc52b23a1ad 100644 --- a/cores/esp32/SHA1Builder.cpp +++ b/libraries/Hash/src/SHA1Builder.cpp @@ -1,27 +1,21 @@ -/* - * FIPS-180-1 compliant SHA-1 implementation - * - * Copyright (C) 2006-2015, ARM Limited, All Rights Reserved - * SPDX-License-Identifier: Apache-2.0 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * This file is part of mbed TLS (https://tls.mbed.org) - * Modified for esp32 by Lucas Saavedra Vaz on 11 Jan 2024 - */ +// Copyright 2024 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Based on mbed TLS (https://tls.mbed.org) #include -#include +#include "SHA1Builder.h" // 32-bit integer manipulation macros (big endian) @@ -251,17 +245,6 @@ void SHA1Builder::add(const uint8_t *data, size_t len) { } } -void SHA1Builder::addHexString(const char *data) { - uint16_t len = strlen(data); - uint8_t *tmp = (uint8_t *)malloc(len / 2); - if (tmp == NULL) { - return; - } - hex2bytes(tmp, len / 2, data); - add(tmp, len / 2); - free(tmp); -} - bool SHA1Builder::addStream(Stream &stream, const size_t maxLen) { const int buf_size = 512; int maxLengthLeft = maxLen; diff --git a/cores/esp32/SHA1Builder.h b/libraries/Hash/src/SHA1Builder.h similarity index 94% rename from cores/esp32/SHA1Builder.h rename to libraries/Hash/src/SHA1Builder.h index b587e4fdc96..203f83a77df 100644 --- a/cores/esp32/SHA1Builder.h +++ b/libraries/Hash/src/SHA1Builder.h @@ -32,19 +32,16 @@ class SHA1Builder : public HashBuilder { void process(const uint8_t *data); public: - void begin() override; - using HashBuilder::add; - void add(const uint8_t *data, size_t len) override; - - using HashBuilder::addHexString; - void addHexString(const char *data) override; + void begin() override; + void add(const uint8_t *data, size_t len) override; bool addStream(Stream &stream, const size_t maxLen) override; void calculate() override; void getBytes(uint8_t *output) override; void getChars(char *output) override; String toString() override; + size_t getHashSize() const override { return SHA1_HASH_SIZE; } }; #endif diff --git a/libraries/Hash/src/SHA2Builder.cpp b/libraries/Hash/src/SHA2Builder.cpp new file mode 100644 index 00000000000..89725e5f920 --- /dev/null +++ b/libraries/Hash/src/SHA2Builder.cpp @@ -0,0 +1,434 @@ +// Copyright 2025 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "esp32-hal-log.h" +#include "SHA2Builder.h" + +// SHA-256 constants +static const uint32_t sha256_k[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 +}; + +// SHA-512 constants +static const uint64_t sha512_k[80] = { + 0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, 0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL, + 0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL, 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL, + 0xd807aa98a3030242ULL, 0x12835b0145706fbeULL, 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL, + 0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, 0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL, + 0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL, 0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL, + 0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL, 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL, + 0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL, 0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL, + 0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL, 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL, + 0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL, 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL, + 0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, 0x81c2c92e47edaee6ULL, 0x92722c851482353bULL, + 0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL, 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL, + 0xd192e819d6ef5218ULL, 0xd69906245565a910ULL, 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL, + 0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, 0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL, + 0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL, 0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL, + 0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL, 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL, + 0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, 0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL, + 0xca273eceea26619cULL, 0xd186b8c721c0c207ULL, 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL, + 0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL, 0x113f9804bef90daeULL, 0x1b710b35131c471bULL, + 0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, 0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL, + 0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL, 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL +}; + +// Macros for bit manipulation +#define ROTR32(x, n) (((x) >> (n)) | ((x) << (32 - (n)))) +#define ROTR64(x, n) (((x) >> (n)) | ((x) << (64 - (n)))) +#define CH32(x, y, z) (((x) & (y)) ^ (~(x) & (z))) +#define CH64(x, y, z) (((x) & (y)) ^ (~(x) & (z))) +#define MAJ32(x, y, z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) +#define MAJ64(x, y, z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) +#define EP0_32(x) (ROTR32(x, 2) ^ ROTR32(x, 13) ^ ROTR32(x, 22)) +#define EP0_64(x) (ROTR64(x, 28) ^ ROTR64(x, 34) ^ ROTR64(x, 39)) +#define EP1_32(x) (ROTR32(x, 6) ^ ROTR32(x, 11) ^ ROTR32(x, 25)) +#define EP1_64(x) (ROTR64(x, 14) ^ ROTR64(x, 18) ^ ROTR64(x, 41)) +#define SIG0_32(x) (ROTR32(x, 7) ^ ROTR32(x, 18) ^ ((x) >> 3)) +#define SIG0_64(x) (ROTR64(x, 1) ^ ROTR64(x, 8) ^ ((x) >> 7)) +#define SIG1_32(x) (ROTR32(x, 17) ^ ROTR32(x, 19) ^ ((x) >> 10)) +#define SIG1_64(x) (ROTR64(x, 19) ^ ROTR64(x, 61) ^ ((x) >> 6)) + +// Byte order conversion +#define BYTESWAP32(x) ((((x) & 0xFF000000) >> 24) | (((x) & 0x00FF0000) >> 8) | (((x) & 0x0000FF00) << 8) | (((x) & 0x000000FF) << 24)) +#define BYTESWAP64(x) (((uint64_t)BYTESWAP32((uint32_t)((x) >> 32))) | (((uint64_t)BYTESWAP32((uint32_t)(x))) << 32)) + +// Constructor +SHA2Builder::SHA2Builder(size_t hash_size) + : hash_size(hash_size), buffer_size(0), finalized(false), total_length(0) { + // Determine block size and algorithm family + if (hash_size == SHA2_224_HASH_SIZE || hash_size == SHA2_256_HASH_SIZE) { + block_size = SHA2_256_BLOCK_SIZE; + is_sha512 = false; + } else if (hash_size == SHA2_384_HASH_SIZE || hash_size == SHA2_512_HASH_SIZE) { + block_size = SHA2_512_BLOCK_SIZE; + is_sha512 = true; + } else { + log_e("Invalid hash size: %d", hash_size); + block_size = 0; + is_sha512 = false; + } +} + +// Initialize the hash computation +void SHA2Builder::begin() { + // Clear the state and buffer + memset(state_32, 0, sizeof(state_32)); + memset(state_64, 0, sizeof(state_64)); + memset(buffer, 0, sizeof(buffer)); + buffer_size = 0; + finalized = false; + total_length = 0; + + // Initialize state based on algorithm + if (!is_sha512) { + // SHA-224/256 initial values + if (hash_size == SHA2_224_HASH_SIZE) { + // SHA-224 initial values + state_32[0] = 0xc1059ed8; + state_32[1] = 0x367cd507; + state_32[2] = 0x3070dd17; + state_32[3] = 0xf70e5939; + state_32[4] = 0xffc00b31; + state_32[5] = 0x68581511; + state_32[6] = 0x64f98fa7; + state_32[7] = 0xbefa4fa4; + } else { + // SHA-256 initial values + state_32[0] = 0x6a09e667; + state_32[1] = 0xbb67ae85; + state_32[2] = 0x3c6ef372; + state_32[3] = 0xa54ff53a; + state_32[4] = 0x510e527f; + state_32[5] = 0x9b05688c; + state_32[6] = 0x1f83d9ab; + state_32[7] = 0x5be0cd19; + } + } else { + // SHA-384/512 initial values + if (hash_size == SHA2_384_HASH_SIZE) { + // SHA-384 initial values + state_64[0] = 0xcbbb9d5dc1059ed8ULL; + state_64[1] = 0x629a292a367cd507ULL; + state_64[2] = 0x9159015a3070dd17ULL; + state_64[3] = 0x152fecd8f70e5939ULL; + state_64[4] = 0x67332667ffc00b31ULL; + state_64[5] = 0x8eb44a8768581511ULL; + state_64[6] = 0xdb0c2e0d64f98fa7ULL; + state_64[7] = 0x47b5481dbefa4fa4ULL; + } else { + // SHA-512 initial values + state_64[0] = 0x6a09e667f3bcc908ULL; + state_64[1] = 0xbb67ae8584caa73bULL; + state_64[2] = 0x3c6ef372fe94f82bULL; + state_64[3] = 0xa54ff53a5f1d36f1ULL; + state_64[4] = 0x510e527fade682d1ULL; + state_64[5] = 0x9b05688c2b3e6c1fULL; + state_64[6] = 0x1f83d9abfb41bd6bULL; + state_64[7] = 0x5be0cd19137e2179ULL; + } + } +} + +// Process a block for SHA-256 +void SHA2Builder::process_block_sha256(const uint8_t *data) { + uint32_t w[64]; + uint32_t a, b, c, d, e, f, g, h; + uint32_t t1, t2; + + // Prepare message schedule + for (int i = 0; i < 16; i++) { + w[i] = BYTESWAP32(((uint32_t*)data)[i]); + } + for (int i = 16; i < 64; i++) { + w[i] = SIG1_32(w[i-2]) + w[i-7] + SIG0_32(w[i-15]) + w[i-16]; + } + + // Initialize working variables + a = state_32[0]; + b = state_32[1]; + c = state_32[2]; + d = state_32[3]; + e = state_32[4]; + f = state_32[5]; + g = state_32[6]; + h = state_32[7]; + + // Main loop + for (int i = 0; i < 64; i++) { + t1 = h + EP1_32(e) + CH32(e, f, g) + sha256_k[i] + w[i]; + t2 = EP0_32(a) + MAJ32(a, b, c); + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + } + + // Add the compressed chunk to the current hash value + state_32[0] += a; + state_32[1] += b; + state_32[2] += c; + state_32[3] += d; + state_32[4] += e; + state_32[5] += f; + state_32[6] += g; + state_32[7] += h; +} + +// Process a block for SHA-512 +void SHA2Builder::process_block_sha512(const uint8_t *data) { + uint64_t w[80]; + uint64_t a, b, c, d, e, f, g, h; + uint64_t t1, t2; + + // Prepare message schedule + for (int i = 0; i < 16; i++) { + w[i] = BYTESWAP64(((uint64_t*)data)[i]); + } + for (int i = 16; i < 80; i++) { + w[i] = SIG1_64(w[i-2]) + w[i-7] + SIG0_64(w[i-15]) + w[i-16]; + } + + // Initialize working variables + a = state_64[0]; + b = state_64[1]; + c = state_64[2]; + d = state_64[3]; + e = state_64[4]; + f = state_64[5]; + g = state_64[6]; + h = state_64[7]; + + // Main loop + for (int i = 0; i < 80; i++) { + t1 = h + EP1_64(e) + CH64(e, f, g) + sha512_k[i] + w[i]; + t2 = EP0_64(a) + MAJ64(a, b, c); + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + } + + // Add the compressed chunk to the current hash value + state_64[0] += a; + state_64[1] += b; + state_64[2] += c; + state_64[3] += d; + state_64[4] += e; + state_64[5] += f; + state_64[6] += g; + state_64[7] += h; +} + +// Add data to the hash computation +void SHA2Builder::add(const uint8_t *data, size_t len) { + if (finalized || len == 0) { + return; + } + + total_length += len; + size_t offset = 0; + + // Process any buffered data first + if (buffer_size > 0) { + size_t to_copy = std::min(len, block_size - buffer_size); + memcpy(buffer + buffer_size, data, to_copy); + buffer_size += to_copy; + offset += to_copy; + + if (buffer_size == block_size) { + if (is_sha512) { + process_block_sha512(buffer); + } else { + process_block_sha256(buffer); + } + buffer_size = 0; + } + } + + // Process full blocks + while (offset + block_size <= len) { + if (is_sha512) { + process_block_sha512(data + offset); + } else { + process_block_sha256(data + offset); + } + offset += block_size; + } + + // Buffer remaining data + if (offset < len) { + memcpy(buffer, data + offset, len - offset); + buffer_size = len - offset; + } +} + +// Add data from a stream +bool SHA2Builder::addStream(Stream &stream, const size_t maxLen) { + const int buf_size = 512; + int maxLengthLeft = maxLen; + uint8_t *buf = (uint8_t *)malloc(buf_size); + + if (!buf) { + return false; + } + + int bytesAvailable = stream.available(); + while ((bytesAvailable > 0) && (maxLengthLeft > 0)) { + // Determine number of bytes to read + int readBytes = bytesAvailable; + if (readBytes > maxLengthLeft) { + readBytes = maxLengthLeft; + } + if (readBytes > buf_size) { + readBytes = buf_size; + } + + // Read data and check if we got something + int numBytesRead = stream.readBytes(buf, readBytes); + if (numBytesRead < 1) { + free(buf); + return false; + } + + // Update SHA2 with buffer payload + add(buf, numBytesRead); + + // Update available number of bytes + maxLengthLeft -= numBytesRead; + bytesAvailable = stream.available(); + } + free(buf); + return true; +} + +// Pad the input according to SHA2 specification +void SHA2Builder::pad() { + // Calculate the number of bytes we have + uint64_t bit_length = total_length * 8; + + // Add the bit '1' to the message + buffer[buffer_size++] = 0x80; + + // Pad with zeros until we have enough space for the length + while (buffer_size + 8 > block_size) { + if (buffer_size < block_size) { + buffer[buffer_size++] = 0x00; + } else { + // Process the block + if (is_sha512) { + process_block_sha512(buffer); + } else { + process_block_sha256(buffer); + } + buffer_size = 0; + } + } + + // Pad with zeros to make room for the length + while (buffer_size + 8 < block_size) { + buffer[buffer_size++] = 0x00; + } + + // Add the length in bits + if (is_sha512) { + // For SHA-512, length is 128 bits (16 bytes) + // We only use the lower 64 bits for now + for (int i = 0; i < 8; i++) { + buffer[block_size - 8 + i] = (uint8_t)(bit_length >> (56 - i * 8)); + } + // Set the upper 64 bits to 0 (for SHA-384/512, length is limited to 2^128-1) + for (int i = 0; i < 8; i++) { + buffer[block_size - 16 + i] = 0x00; + } + } else { + // For SHA-256, length is 64 bits (8 bytes) + for (int i = 0; i < 8; i++) { + buffer[block_size - 8 + i] = (uint8_t)(bit_length >> (56 - i * 8)); + } + } +} + +// Finalize the hash computation +void SHA2Builder::calculate() { + if (finalized) { + return; + } + + // Pad the input + pad(); + + // Process the final block + if (is_sha512) { + process_block_sha512(buffer); + } else { + process_block_sha256(buffer); + } + + // Extract bytes from the state + if (is_sha512) { + for (size_t i = 0; i < hash_size; i++) { + hash[i] = (uint8_t)(state_64[i >> 3] >> (56 - ((i & 0x7) << 3))); + } + } else { + for (size_t i = 0; i < hash_size; i++) { + hash[i] = (uint8_t)(state_32[i >> 2] >> (24 - ((i & 0x3) << 3))); + } + } + + finalized = true; +} + +// Get the hash as bytes +void SHA2Builder::getBytes(uint8_t *output) { + memcpy(output, hash, hash_size); +} + +// Get the hash as hex string +void SHA2Builder::getChars(char *output) { + bytes2hex(output, hash_size * 2 + 1, hash, hash_size); +} + +// Get the hash as String +String SHA2Builder::toString() { + char out[(hash_size * 2) + 1]; + getChars(out); + return String(out); +} diff --git a/libraries/Hash/src/SHA2Builder.h b/libraries/Hash/src/SHA2Builder.h new file mode 100644 index 00000000000..228d58a157c --- /dev/null +++ b/libraries/Hash/src/SHA2Builder.h @@ -0,0 +1,96 @@ +// Copyright 2025 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SHA2Builder_h +#define SHA2Builder_h + +#include +#include + +#include "HashBuilder.h" + +// SHA2 constants +#define SHA2_224_HASH_SIZE 28 +#define SHA2_256_HASH_SIZE 32 +#define SHA2_384_HASH_SIZE 48 +#define SHA2_512_HASH_SIZE 64 + +#define SHA2_224_BLOCK_SIZE 64 +#define SHA2_256_BLOCK_SIZE 64 +#define SHA2_384_BLOCK_SIZE 128 +#define SHA2_512_BLOCK_SIZE 128 + +// SHA2 state sizes (in 32-bit words for SHA-224/256, 64-bit words for SHA-384/512) +#define SHA2_224_STATE_SIZE 8 +#define SHA2_256_STATE_SIZE 8 +#define SHA2_384_STATE_SIZE 8 +#define SHA2_512_STATE_SIZE 8 + +class SHA2Builder : public HashBuilder { +protected: + uint32_t state_32[8]; // SHA-224/256 state (256 bits) + uint64_t state_64[8]; // SHA-384/512 state (512 bits) + uint8_t buffer[128]; // Input buffer (max block size) + size_t block_size; // Block size + size_t hash_size; // Output hash size + size_t buffer_size; // Current buffer size + bool finalized; // Whether hash has been finalized + bool is_sha512; // Whether using SHA-512 family + uint8_t hash[64]; // Hash result + uint64_t total_length; // Total length of input data + + void process_block_sha256(const uint8_t *data); + void process_block_sha512(const uint8_t *data); + void pad(); + +public: + using HashBuilder::add; + + SHA2Builder(size_t hash_size = SHA2_256_HASH_SIZE); + virtual ~SHA2Builder() {} + + void begin() override; + void add(const uint8_t *data, size_t len) override; + bool addStream(Stream &stream, const size_t maxLen) override; + void calculate() override; + void getBytes(uint8_t *output) override; + void getChars(char *output) override; + String toString() override; + + size_t getHashSize() const override { + return hash_size; + } +}; + +class SHA224Builder : public SHA2Builder { +public: + SHA224Builder() : SHA2Builder(SHA2_224_HASH_SIZE) {} +}; + +class SHA256Builder : public SHA2Builder { +public: + SHA256Builder() : SHA2Builder(SHA2_256_HASH_SIZE) {} +}; + +class SHA384Builder : public SHA2Builder { +public: + SHA384Builder() : SHA2Builder(SHA2_384_HASH_SIZE) {} +}; + +class SHA512Builder : public SHA2Builder { +public: + SHA512Builder() : SHA2Builder(SHA2_512_HASH_SIZE) {} +}; + +#endif diff --git a/libraries/Hash/src/SHA3Builder.cpp b/libraries/Hash/src/SHA3Builder.cpp new file mode 100644 index 00000000000..ef05f27fa37 --- /dev/null +++ b/libraries/Hash/src/SHA3Builder.cpp @@ -0,0 +1,264 @@ +// Copyright 2025 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "esp32-hal-log.h" +#include "SHA3Builder.h" + +// Keccak round constants +static const uint64_t keccak_round_constants[24] = { + 0x0000000000000001ULL, 0x0000000000008082ULL, 0x800000000000808AULL, + 0x8000000080008000ULL, 0x000000000000808BULL, 0x0000000080000001ULL, + 0x8000000080008081ULL, 0x8000000000008009ULL, 0x000000000000008AULL, + 0x0000000000000088ULL, 0x0000000080008009ULL, 0x000000008000000AULL, + 0x000000008000808BULL, 0x800000000000008BULL, 0x8000000000008089ULL, + 0x8000000000008003ULL, 0x8000000000008002ULL, 0x8000000000000080ULL, + 0x000000000000800AULL, 0x800000008000000AULL, 0x8000000080008081ULL, + 0x8000000000008080ULL, 0x0000000080000001ULL, 0x8000000080008008ULL +}; + +// Rho rotation constants +static const uint32_t rho[6] = { + 0x3f022425, 0x1c143a09, 0x2c3d3615, 0x27191713, 0x312b382e, 0x3e030832 +}; + +// Pi permutation constants +static const uint32_t pi[6] = { + 0x110b070a, 0x10050312, 0x04181508, 0x0d13170f, 0x0e14020c, 0x01060916 +}; + +// Macros for bit manipulation +#define ROTR64(x, y) (((x) << (64U - (y))) | ((x) >> (y))) + +// Keccak-f permutation +void SHA3Builder::keccak_f(uint64_t state[25]) { + uint64_t lane[5]; + uint64_t *s = state; + int i; + + for (int round = 0; round < 24; round++) { + uint64_t t; + + // Theta step + for (i = 0; i < 5; i++) { + lane[i] = s[i] ^ s[i + 5] ^ s[i + 10] ^ s[i + 15] ^ s[i + 20]; + } + for (i = 0; i < 5; i++) { + t = lane[(i + 4) % 5] ^ ROTR64(lane[(i + 1) % 5], 63); + s[i] ^= t; s[i + 5] ^= t; s[i + 10] ^= t; s[i + 15] ^= t; s[i + 20] ^= t; + } + + // Rho step + for (i = 1; i < 25; i += 4) { + uint32_t r = rho[(i - 1) >> 2]; + for (int j = i; j < i + 4; j++) { + uint8_t r8 = (uint8_t) (r >> 24); + r <<= 8; + s[j] = ROTR64(s[j], r8); + } + } + + // Pi step + t = s[1]; + for (i = 0; i < 24; i += 4) { + uint32_t p = pi[i >> 2]; + for (unsigned j = 0; j < 4; j++) { + uint64_t tmp = s[p & 0xff]; + s[p & 0xff] = t; + t = tmp; + p >>= 8; + } + } + + // Chi step + for (i = 0; i <= 20; i += 5) { + lane[0] = s[i]; lane[1] = s[i + 1]; lane[2] = s[i + 2]; + lane[3] = s[i + 3]; lane[4] = s[i + 4]; + s[i + 0] ^= (~lane[1]) & lane[2]; + s[i + 1] ^= (~lane[2]) & lane[3]; + s[i + 2] ^= (~lane[3]) & lane[4]; + s[i + 3] ^= (~lane[4]) & lane[0]; + s[i + 4] ^= (~lane[0]) & lane[1]; + } + + // Iota step + s[0] ^= keccak_round_constants[round]; + } +} + +// Process a block of data +void SHA3Builder::process_block(const uint8_t *data) { + // XOR the data into the state using byte-level operations + for (size_t i = 0; i < rate; i++) { + size_t state_idx = i >> 3; // i / 8 + size_t bit_offset = (i & 0x7) << 3; // (i % 8) * 8 + uint64_t byte_val = (uint64_t)data[i] << bit_offset; + state[state_idx] ^= byte_val; + } + + // Apply Keccak-f permutation + keccak_f(state); +} + +// Pad the input according to SHA3 specification +void SHA3Builder::pad() { + // Clear the buffer first + memset(buffer + buffer_size, 0, rate - buffer_size); + + // Add the domain separator (0x06) at the current position + buffer[buffer_size] = 0x06; + + // Set the last byte to indicate the end (0x80) + buffer[rate - 1] = 0x80; +} + +// Constructor +SHA3Builder::SHA3Builder(size_t hash_size) + : hash_size(hash_size), buffer_size(0), finalized(false) { + // Calculate rate based on hash size + if (hash_size == SHA3_224_HASH_SIZE) { + rate = SHA3_224_RATE; + } else if (hash_size == SHA3_256_HASH_SIZE) { + rate = SHA3_256_RATE; + } else if (hash_size == SHA3_384_HASH_SIZE) { + rate = SHA3_384_RATE; + } else if (hash_size == SHA3_512_HASH_SIZE) { + rate = SHA3_512_RATE; + } else { + log_e("Invalid hash size: %d", hash_size); + rate = 0; // Invalid hash size + } +} + +// Initialize the hash computation +void SHA3Builder::begin() { + // Clear the state + memset(state, 0, sizeof(state)); + memset(buffer, 0, sizeof(buffer)); + buffer_size = 0; + finalized = false; +} + +// Add data to the hash computation +void SHA3Builder::add(const uint8_t *data, size_t len) { + if (finalized || len == 0) { + return; + } + + size_t offset = 0; + + // Process any buffered data first + if (buffer_size > 0) { + size_t to_copy = std::min(len, rate - buffer_size); + memcpy(buffer + buffer_size, data, to_copy); + buffer_size += to_copy; + offset += to_copy; + + if (buffer_size == rate) { + process_block(buffer); + buffer_size = 0; + } + } + + // Process full blocks + while (offset + rate <= len) { + process_block(data + offset); + offset += rate; + } + + // Buffer remaining data + if (offset < len) { + memcpy(buffer, data + offset, len - offset); + buffer_size = len - offset; + } +} + +// Add data from a stream +bool SHA3Builder::addStream(Stream &stream, const size_t maxLen) { + const int buf_size = 512; + int maxLengthLeft = maxLen; + uint8_t *buf = (uint8_t *)malloc(buf_size); + + if (!buf) { + return false; + } + + int bytesAvailable = stream.available(); + while ((bytesAvailable > 0) && (maxLengthLeft > 0)) { + // Determine number of bytes to read + int readBytes = bytesAvailable; + if (readBytes > maxLengthLeft) { + readBytes = maxLengthLeft; + } + if (readBytes > buf_size) { + readBytes = buf_size; + } + + // Read data and check if we got something + int numBytesRead = stream.readBytes(buf, readBytes); + if (numBytesRead < 1) { + free(buf); + return false; + } + + // Update SHA3 with buffer payload + add(buf, numBytesRead); + + // Update available number of bytes + maxLengthLeft -= numBytesRead; + bytesAvailable = stream.available(); + } + free(buf); + return true; +} + +// Finalize the hash computation +void SHA3Builder::calculate() { + if (finalized) { + return; + } + + // Pad the input + pad(); + + // Process the final block + process_block(buffer); + + // Extract bytes from the state + for (size_t i = 0; i < hash_size; i++) { + size_t state_idx = i >> 3; // i / 8 + size_t bit_offset = (i & 0x7) << 3; // (i % 8) * 8 + hash[i] = (uint8_t)(state[state_idx] >> bit_offset); + } + + finalized = true; +} + +// Get the hash as bytes +void SHA3Builder::getBytes(uint8_t *output) { + memcpy(output, hash, hash_size); +} + +// Get the hash as hex string +void SHA3Builder::getChars(char *output) { + bytes2hex(output, hash_size * 2 + 1, hash, hash_size); +} + +// Get the hash as String +String SHA3Builder::toString() { + char out[(hash_size * 2) + 1]; + getChars(out); + return String(out); +} diff --git a/libraries/Hash/src/SHA3Builder.h b/libraries/Hash/src/SHA3Builder.h new file mode 100644 index 00000000000..67837d05076 --- /dev/null +++ b/libraries/Hash/src/SHA3Builder.h @@ -0,0 +1,89 @@ +// Copyright 2025 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SHA3Builder_h +#define SHA3Builder_h + +#include +#include + +#include "HashBuilder.h" + +// SHA3 constants +#define SHA3_224_HASH_SIZE 28 +#define SHA3_256_HASH_SIZE 32 +#define SHA3_384_HASH_SIZE 48 +#define SHA3_512_HASH_SIZE 64 + +#define SHA3_224_RATE 144 +#define SHA3_256_RATE 136 +#define SHA3_384_RATE 104 +#define SHA3_512_RATE 72 + +#define SHA3_STATE_SIZE 200 // 1600 bits = 200 bytes + +class SHA3Builder : public HashBuilder { +protected: + uint64_t state[25]; // SHA3 state (1600 bits) + uint8_t buffer[200]; // Input buffer + size_t rate; // Rate (block size) + size_t hash_size; // Output hash size + size_t buffer_size; // Current buffer size + bool finalized; // Whether hash has been finalized + uint8_t hash[64]; // Hash result + + void keccak_f(uint64_t state[25]); + void process_block(const uint8_t *data); + void pad(); + +public: + using HashBuilder::add; + + SHA3Builder(size_t hash_size = SHA3_256_HASH_SIZE); + virtual ~SHA3Builder() {} + + void begin() override; + void add(const uint8_t *data, size_t len) override; + bool addStream(Stream &stream, const size_t maxLen) override; + void calculate() override; + void getBytes(uint8_t *output) override; + void getChars(char *output) override; + String toString() override; + + size_t getHashSize() const override { + return hash_size; + } +}; + +class SHA3_224Builder : public SHA3Builder { +public: + SHA3_224Builder() : SHA3Builder(SHA3_224_HASH_SIZE) {} +}; + +class SHA3_256Builder : public SHA3Builder { +public: + SHA3_256Builder() : SHA3Builder(SHA3_256_HASH_SIZE) {} +}; + +class SHA3_384Builder : public SHA3Builder { +public: + SHA3_384Builder() : SHA3Builder(SHA3_384_HASH_SIZE) {} +}; + +class SHA3_512Builder : public SHA3Builder { +public: + SHA3_512Builder() : SHA3Builder(SHA3_512_HASH_SIZE) {} +}; + +#endif