Skip to content

feat(hash): Add hashing library and new algorithms #11676

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -93,6 +93,7 @@ set(ARDUINO_ALL_LIBRARIES
Ethernet
FFat
FS
Hash
HTTPClient
HTTPUpdate
Insights
Expand Down Expand Up @@ -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)
Expand Down
3 changes: 1 addition & 2 deletions cores/esp32/HEXBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include <Arduino.h>
#include <HEXBuilder.h>
#include "HEXBuilder.h"

static uint8_t hex_char_to_byte(uint8_t c) {
return (c >= 'a' && c <= 'f') ? (c - ((uint8_t)'a' - 0xa))
Expand Down
2 changes: 2 additions & 0 deletions cores/esp32/HEXBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
#include <WString.h>
#include <Stream.h>

// 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);
Expand Down
38 changes: 38 additions & 0 deletions cores/esp32/HashBuilder.cpp
Original file line number Diff line number Diff line change
@@ -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());
}
19 changes: 8 additions & 11 deletions cores/esp32/HashBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
16 changes: 2 additions & 14 deletions cores/esp32/MD5Builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include <Arduino.h>
#include <HEXBuilder.h>
#include <MD5Builder.h>
#include "HEXBuilder.h"
#include "MD5Builder.h"

void MD5Builder::begin(void) {
memset(_buf, 0x00, ESP_ROM_MD5_DIGEST_LEN);
Expand All @@ -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;
Expand Down
9 changes: 3 additions & 6 deletions cores/esp32/MD5Builder.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
@@ -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 <HEXBuilder.h>

void setup() {
Expand Down
178 changes: 178 additions & 0 deletions libraries/Hash/examples/PBKDF2_HMAC/PBKDF2_HMAC.ino
Original file line number Diff line number Diff line change
@@ -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 <SHA1Builder.h>
#include <SHA2Builder.h>
#include <PBKDF2_HMACBuilder.h>

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
}
Loading
Loading