mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Add sha256 support
This is a breakout from https://github.com/esphome/esphome/pull/10809
This commit is contained in:
		
							
								
								
									
										22
									
								
								esphome/components/sha256/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								esphome/components/sha256/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.core import CORE | ||||||
|  | from esphome.helpers import IS_MACOS | ||||||
|  | from esphome.types import ConfigType | ||||||
|  |  | ||||||
|  | CODEOWNERS = ["@esphome/core"] | ||||||
|  |  | ||||||
|  | sha256_ns = cg.esphome_ns.namespace("sha256") | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.Schema({}) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config: ConfigType) -> None: | ||||||
|  |     # Add OpenSSL library for host platform | ||||||
|  |     if not CORE.is_host: | ||||||
|  |         return | ||||||
|  |     if IS_MACOS: | ||||||
|  |         # macOS needs special handling for Homebrew OpenSSL | ||||||
|  |         cg.add_build_flag("-I/opt/homebrew/opt/openssl/include") | ||||||
|  |         cg.add_build_flag("-L/opt/homebrew/opt/openssl/lib") | ||||||
|  |     cg.add_build_flag("-lcrypto") | ||||||
							
								
								
									
										83
									
								
								esphome/components/sha256/sha256.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								esphome/components/sha256/sha256.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,83 @@ | |||||||
|  | #include "sha256.h" | ||||||
|  |  | ||||||
|  | // Only compile SHA256 implementation on platforms that support it | ||||||
|  | #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_HOST) | ||||||
|  |  | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  | #include <cstring> | ||||||
|  |  | ||||||
|  | namespace esphome::sha256 { | ||||||
|  |  | ||||||
|  | #if defined(USE_ESP32) || defined(USE_LIBRETINY) | ||||||
|  |  | ||||||
|  | SHA256::~SHA256() { mbedtls_sha256_free(&this->ctx_); } | ||||||
|  |  | ||||||
|  | void SHA256::init() { | ||||||
|  |   mbedtls_sha256_init(&this->ctx_); | ||||||
|  |   mbedtls_sha256_starts(&this->ctx_, 0);  // 0 = SHA256, not SHA224 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SHA256::add(const uint8_t *data, size_t len) { mbedtls_sha256_update(&this->ctx_, data, len); } | ||||||
|  |  | ||||||
|  | void SHA256::calculate() { mbedtls_sha256_finish(&this->ctx_, this->digest_); } | ||||||
|  |  | ||||||
|  | #elif defined(USE_ESP8266) || defined(USE_RP2040) | ||||||
|  |  | ||||||
|  | SHA256::~SHA256() = default; | ||||||
|  |  | ||||||
|  | void SHA256::init() { | ||||||
|  |   br_sha256_init(&this->ctx_); | ||||||
|  |   this->calculated_ = false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SHA256::add(const uint8_t *data, size_t len) { br_sha256_update(&this->ctx_, data, len); } | ||||||
|  |  | ||||||
|  | void SHA256::calculate() { | ||||||
|  |   if (!this->calculated_) { | ||||||
|  |     br_sha256_out(&this->ctx_, this->digest_); | ||||||
|  |     this->calculated_ = true; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #elif defined(USE_HOST) | ||||||
|  |  | ||||||
|  | SHA256::~SHA256() { | ||||||
|  |   if (this->ctx_) { | ||||||
|  |     EVP_MD_CTX_free(this->ctx_); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SHA256::init() { | ||||||
|  |   if (this->ctx_) { | ||||||
|  |     EVP_MD_CTX_free(this->ctx_); | ||||||
|  |   } | ||||||
|  |   this->ctx_ = EVP_MD_CTX_new(); | ||||||
|  |   EVP_DigestInit_ex(this->ctx_, EVP_sha256(), nullptr); | ||||||
|  |   this->calculated_ = false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SHA256::add(const uint8_t *data, size_t len) { | ||||||
|  |   if (!this->ctx_) { | ||||||
|  |     this->init(); | ||||||
|  |   } | ||||||
|  |   EVP_DigestUpdate(this->ctx_, data, len); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SHA256::calculate() { | ||||||
|  |   if (!this->ctx_) { | ||||||
|  |     this->init(); | ||||||
|  |   } | ||||||
|  |   if (!this->calculated_) { | ||||||
|  |     unsigned int len = 32; | ||||||
|  |     EVP_DigestFinal_ex(this->ctx_, this->digest_, &len); | ||||||
|  |     this->calculated_ = true; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #else | ||||||
|  | #error "SHA256 not supported on this platform" | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | }  // namespace esphome::sha256 | ||||||
|  |  | ||||||
|  | #endif  // Platform check | ||||||
							
								
								
									
										56
									
								
								esphome/components/sha256/sha256.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								esphome/components/sha256/sha256.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/defines.h" | ||||||
|  |  | ||||||
|  | // Only define SHA256 on platforms that support it | ||||||
|  | #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_HOST) | ||||||
|  |  | ||||||
|  | #include <cstdint> | ||||||
|  | #include <string> | ||||||
|  | #include <memory> | ||||||
|  | #include "esphome/core/hash_base.h" | ||||||
|  |  | ||||||
|  | #if defined(USE_ESP32) || defined(USE_LIBRETINY) | ||||||
|  | #include "mbedtls/sha256.h" | ||||||
|  | #elif defined(USE_ESP8266) || defined(USE_RP2040) | ||||||
|  | #include <bearssl/bearssl_hash.h> | ||||||
|  | #elif defined(USE_HOST) | ||||||
|  | #include <openssl/evp.h> | ||||||
|  | #else | ||||||
|  | #error "SHA256 not supported on this platform" | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | namespace esphome::sha256 { | ||||||
|  |  | ||||||
|  | class SHA256 : public esphome::HashBase { | ||||||
|  |  public: | ||||||
|  |   SHA256() = default; | ||||||
|  |   ~SHA256() override; | ||||||
|  |  | ||||||
|  |   void init() override; | ||||||
|  |   void add(const uint8_t *data, size_t len) override; | ||||||
|  |   using HashBase::add;  // Bring base class overload into scope | ||||||
|  |   void add(const std::string &data) { this->add((const uint8_t *) data.c_str(), data.length()); } | ||||||
|  |  | ||||||
|  |   void calculate() override; | ||||||
|  |  | ||||||
|  |   /// Get the size of the hash in bytes (32 for SHA256) | ||||||
|  |   size_t get_size() const override { return 32; } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  | #if defined(USE_ESP32) || defined(USE_LIBRETINY) | ||||||
|  |   mbedtls_sha256_context ctx_{}; | ||||||
|  | #elif defined(USE_ESP8266) || defined(USE_RP2040) | ||||||
|  |   br_sha256_context ctx_{}; | ||||||
|  |   bool calculated_{false}; | ||||||
|  | #elif defined(USE_HOST) | ||||||
|  |   EVP_MD_CTX *ctx_{nullptr}; | ||||||
|  |   bool calculated_{false}; | ||||||
|  | #else | ||||||
|  | #error "SHA256 not supported on this platform" | ||||||
|  | #endif | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace esphome::sha256 | ||||||
|  |  | ||||||
|  | #endif  // Platform check | ||||||
| @@ -116,6 +116,7 @@ | |||||||
| #define USE_API_PLAINTEXT | #define USE_API_PLAINTEXT | ||||||
| #define USE_API_SERVICES | #define USE_API_SERVICES | ||||||
| #define USE_MD5 | #define USE_MD5 | ||||||
|  | #define USE_SHA256 | ||||||
| #define USE_MQTT | #define USE_MQTT | ||||||
| #define USE_NETWORK | #define USE_NETWORK | ||||||
| #define USE_ONLINE_IMAGE_BMP_SUPPORT | #define USE_ONLINE_IMAGE_BMP_SUPPORT | ||||||
|   | |||||||
							
								
								
									
										56
									
								
								esphome/core/hash_base.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								esphome/core/hash_base.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include <cstdint> | ||||||
|  | #include <cstddef> | ||||||
|  | #include <cstring> | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  |  | ||||||
|  | /// Base class for hash algorithms | ||||||
|  | class HashBase { | ||||||
|  |  public: | ||||||
|  |   virtual ~HashBase() = default; | ||||||
|  |  | ||||||
|  |   /// Initialize a new hash computation | ||||||
|  |   virtual void init() = 0; | ||||||
|  |  | ||||||
|  |   /// Add bytes of data for the hash | ||||||
|  |   virtual void add(const uint8_t *data, size_t len) = 0; | ||||||
|  |   void add(const char *data, size_t len) { this->add((const uint8_t *) data, len); } | ||||||
|  |  | ||||||
|  |   /// Compute the hash based on provided data | ||||||
|  |   virtual void calculate() = 0; | ||||||
|  |  | ||||||
|  |   /// Retrieve the hash as bytes | ||||||
|  |   void get_bytes(uint8_t *output) { memcpy(output, this->digest_, this->get_size()); } | ||||||
|  |  | ||||||
|  |   /// Retrieve the hash as hex characters | ||||||
|  |   void get_hex(char *output) { | ||||||
|  |     for (size_t i = 0; i < this->get_size(); i++) { | ||||||
|  |       uint8_t byte = this->digest_[i]; | ||||||
|  |       output[i * 2] = format_hex_char(byte >> 4); | ||||||
|  |       output[i * 2 + 1] = format_hex_char(byte & 0x0F); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /// Compare the hash against a provided byte-encoded hash | ||||||
|  |   bool equals_bytes(const uint8_t *expected) { return memcmp(this->digest_, expected, this->get_size()) == 0; } | ||||||
|  |  | ||||||
|  |   /// Compare the hash against a provided hex-encoded hash | ||||||
|  |   bool equals_hex(const char *expected) { | ||||||
|  |     uint8_t parsed[32];  // Max size for SHA256 | ||||||
|  |     if (!parse_hex(expected, parsed, this->get_size())) { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |     return this->equals_bytes(parsed); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /// Get the size of the hash in bytes (16 for MD5, 32 for SHA256) | ||||||
|  |   virtual size_t get_size() const = 0; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   uint8_t digest_[32];  // Common digest storage, sized for largest hash (SHA256) | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace esphome | ||||||
| @@ -82,6 +82,16 @@ template<typename T> constexpr T byteswap(T n) { | |||||||
|   return m; |   return m; | ||||||
| } | } | ||||||
| template<> constexpr uint8_t byteswap(uint8_t n) { return n; } | template<> constexpr uint8_t byteswap(uint8_t n) { return n; } | ||||||
|  | #ifdef USE_LIBRETINY | ||||||
|  | // LibreTiny's Beken framework redefines __builtin_bswap functions as non-constexpr | ||||||
|  | template<> inline uint16_t byteswap(uint16_t n) { return __builtin_bswap16(n); } | ||||||
|  | template<> inline uint32_t byteswap(uint32_t n) { return __builtin_bswap32(n); } | ||||||
|  | template<> inline uint64_t byteswap(uint64_t n) { return __builtin_bswap64(n); } | ||||||
|  | template<> inline int8_t byteswap(int8_t n) { return n; } | ||||||
|  | template<> inline int16_t byteswap(int16_t n) { return __builtin_bswap16(n); } | ||||||
|  | template<> inline int32_t byteswap(int32_t n) { return __builtin_bswap32(n); } | ||||||
|  | template<> inline int64_t byteswap(int64_t n) { return __builtin_bswap64(n); } | ||||||
|  | #else | ||||||
| template<> constexpr uint16_t byteswap(uint16_t n) { return __builtin_bswap16(n); } | template<> constexpr uint16_t byteswap(uint16_t n) { return __builtin_bswap16(n); } | ||||||
| template<> constexpr uint32_t byteswap(uint32_t n) { return __builtin_bswap32(n); } | template<> constexpr uint32_t byteswap(uint32_t n) { return __builtin_bswap32(n); } | ||||||
| template<> constexpr uint64_t byteswap(uint64_t n) { return __builtin_bswap64(n); } | template<> constexpr uint64_t byteswap(uint64_t n) { return __builtin_bswap64(n); } | ||||||
| @@ -89,6 +99,7 @@ template<> constexpr int8_t byteswap(int8_t n) { return n; } | |||||||
| template<> constexpr int16_t byteswap(int16_t n) { return __builtin_bswap16(n); } | template<> constexpr int16_t byteswap(int16_t n) { return __builtin_bswap16(n); } | ||||||
| template<> constexpr int32_t byteswap(int32_t n) { return __builtin_bswap32(n); } | template<> constexpr int32_t byteswap(int32_t n) { return __builtin_bswap32(n); } | ||||||
| template<> constexpr int64_t byteswap(int64_t n) { return __builtin_bswap64(n); } | template<> constexpr int64_t byteswap(int64_t n) { return __builtin_bswap64(n); } | ||||||
|  | #endif | ||||||
|  |  | ||||||
| ///@} | ///@} | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										32
									
								
								tests/components/sha256/common.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								tests/components/sha256/common.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | esphome: | ||||||
|  |   on_boot: | ||||||
|  |     - lambda: |- | ||||||
|  |         // Test SHA256 functionality | ||||||
|  |         #ifdef USE_SHA256 | ||||||
|  |         using esphome::sha256::SHA256; | ||||||
|  |         SHA256 hasher; | ||||||
|  |         hasher.init(); | ||||||
|  |  | ||||||
|  |         // Test with "Hello World" - known SHA256 | ||||||
|  |         const char* test_string = "Hello World"; | ||||||
|  |         hasher.add(test_string, strlen(test_string)); | ||||||
|  |         hasher.calculate(); | ||||||
|  |  | ||||||
|  |         char hex_output[65]; | ||||||
|  |         hasher.get_hex(hex_output); | ||||||
|  |         hex_output[64] = '\0'; | ||||||
|  |  | ||||||
|  |         ESP_LOGD("SHA256", "SHA256('Hello World') = %s", hex_output); | ||||||
|  |  | ||||||
|  |         // Expected: a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e | ||||||
|  |         const char* expected = "a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e"; | ||||||
|  |         if (strcmp(hex_output, expected) == 0) { | ||||||
|  |           ESP_LOGI("SHA256", "Test PASSED"); | ||||||
|  |         } else { | ||||||
|  |           ESP_LOGE("SHA256", "Test FAILED. Expected %s", expected); | ||||||
|  |         } | ||||||
|  |         #else | ||||||
|  |         ESP_LOGW("SHA256", "SHA256 not available on this platform"); | ||||||
|  |         #endif | ||||||
|  |  | ||||||
|  | sha256: | ||||||
							
								
								
									
										1
									
								
								tests/components/sha256/test.bk72xx-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tests/components/sha256/test.bk72xx-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										1
									
								
								tests/components/sha256/test.esp32-idf.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tests/components/sha256/test.esp32-idf.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										1
									
								
								tests/components/sha256/test.esp8266-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tests/components/sha256/test.esp8266-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										1
									
								
								tests/components/sha256/test.host.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tests/components/sha256/test.host.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										1
									
								
								tests/components/sha256/test.rp2040-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tests/components/sha256/test.rp2040-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | <<: !include common.yaml | ||||||
		Reference in New Issue
	
	Block a user