Home IO Control
ESPHome add-on for IO-Homecontrol devices
Loading...
Searching...
No Matches
proto_crypto.h
Go to the documentation of this file.
1#pragma once
2
3/// @file proto_crypto.h
4/// @brief Cryptographic helpers for the IO‑Homecontrol protocol.
5///
6/// IO‑Homecontrol uses AES‑128 encryption and a proprietary 6‑byte HMAC construction
7/// derived from the original Somfy implementation. The "HMAC" here is not a standard
8/// HMAC-SHA; it is a custom construction: the IV is built from frame bytes and checksums,
9/// then encrypted with the system key via AES‑128‑ECB, and the first 6 bytes are taken.
10///
11/// During pairing, the system key itself is transferred to the device using an XOR‑AES
12/// obfuscation with a globally shared transfer key. crypt_key() handles both encryption
13/// and decryption; the operation is symmetric.
14///
15/// @warning The transfer key (TRANSFER_KEY) is hardcoded and public—its only purpose
16/// is to obfuscate the system key during over‑the‑air transfer. The security
17/// of the installation relies entirely on keeping the system key secret.
18
19#include "proto_frame.h"
20
21namespace esphome {
22namespace home_io_control {
23namespace crypto {
24
25/// Update running checksum bytes (c1, c2) with a new data byte (IO‑Homecontrol IV derivation).
26/// @param byte Input data byte.
27/// @param c1 First checksum byte (inout).
28/// @param c2 Second checksum byte (inout).
29void compute_checksum(uint8_t byte, uint8_t &c1, uint8_t &c2);
30
31/// Construct the 16‑byte IV for AES encryption from frame data and challenge.
32/// Layout: bytes 0–7 = up to 8 frame bytes padded with 0x55, bytes 8–9 = checksums,
33/// bytes 10–15 = challenge.
34/// @param data Frame data bytes.
35/// @param len Number of data bytes.
36/// @param challenge 6‑byte challenge from the device.
37/// @param iv Output: 16‑byte IV.
38void construct_iv(const uint8_t *data, uint8_t len, const uint8_t challenge[HMAC_SIZE], uint8_t iv[IV_SIZE]);
39
40/// AES‑128 ECB encrypt a single 16‑byte block.
41/// @param in 16‑byte plaintext block.
42/// @param key 16‑byte AES key.
43/// @param out Output: 16‑byte ciphertext block.
44/// @return true on success.
45bool aes128_encrypt(const uint8_t in[AES_BLOCK_SIZE], const uint8_t key[AES_KEY_SIZE], uint8_t out[AES_BLOCK_SIZE]);
46
47/// AES‑128 ECB decrypt a single 16‑byte block.
48/// @param in 16‑byte ciphertext block.
49/// @param key 16‑byte AES key.
50/// @param out Output: 16‑byte plaintext block.
51/// @return true on success.
52bool aes128_decrypt(const uint8_t in[AES_BLOCK_SIZE], const uint8_t key[AES_KEY_SIZE], uint8_t out[AES_BLOCK_SIZE]);
53
54/// Create a 6‑byte HMAC for authentication (IO‑Homecontrol proprietary scheme).
55/// Process: build IV from [data + challenge] → AES‑128‑ECB encrypt IV with system key → take first 6 bytes.
56/// This is NOT a standard HMAC; it is specific to IO‑Homecontrol and matches
57/// the protocol specification as implemented in compatible devices.
58/// @param data Frame data bytes (usually command + payload).
59/// @param len Length of data.
60/// @param challenge 6‑byte random challenge.
61/// @param key 16‑byte system key.
62/// @param hmac Output: 6‑byte HMAC.
63/// @note The IV construction appends two checksum bytes derived from the data stream
64/// (see compute_checksum()) followed by the 6‑byte challenge, padded to 16 bytes
65/// with 0x55. The AES result is truncated to 6 bytes for transmission.
66/// @return true on success.
67bool create_hmac(const uint8_t *data, uint8_t len, const uint8_t challenge[HMAC_SIZE], const uint8_t key[AES_KEY_SIZE],
68 uint8_t hmac[HMAC_SIZE]);
69
70/// Verify a received 6‑byte HMAC using constant‑time comparison.
71/// @param data Frame data bytes.
72/// @param len Length of data.
73/// @param hmac Received 6‑byte HMAC.
74/// @param challenge Challenge used in HMAC calculation.
75/// @param key 16‑byte system key.
76/// @return true if HMAC matches.
77bool verify_hmac(const uint8_t *data, uint8_t len, const uint8_t hmac[HMAC_SIZE], const uint8_t challenge[HMAC_SIZE],
78 const uint8_t key[AES_KEY_SIZE]);
79
80/// Encrypt or decrypt the system key during pairing (XOR with AES‑encrypted IV).
81/// The same operation works for both directions: encrypting for the device and
82/// decrypting the device's acknowledgement.
83/// @param data Frame data (typically the key‑init command byte).
84/// @param len Length of data (usually 1).
85/// @param challenge Device's 6‑byte challenge.
86/// @param in Input key (plaintext for encrypt, ciphertext for decrypt).
87/// @param out Output key.
88/// @warning This primitive is used ONLY during the key‑transfer phase (0x32). The
89/// resulting system key is then used for all normal authenticated exchanges.
90/// Never call this with arbitrary data outside the pairing sequence.
91/// @return true on success.
92bool crypt_key(const uint8_t *data, uint8_t len, const uint8_t challenge[HMAC_SIZE], const uint8_t in[AES_KEY_SIZE],
93 uint8_t out[AES_KEY_SIZE]);
94
95/// Generate 6 random bytes for a challenge using the ESP hardware RNG.
96/// @param out Output buffer (6 bytes).
97void generate_challenge(uint8_t out[HMAC_SIZE]);
98
99} // namespace crypto
100} // namespace home_io_control
101} // namespace esphome
void compute_checksum(uint8_t byte, uint8_t &c1, uint8_t &c2)
Proprietary checksum algorithm used in IV construction.
void generate_challenge(uint8_t out[HMAC_SIZE])
Generate 6 random bytes for a challenge using the ESP32 hardware RNG.
bool create_hmac(const uint8_t *data, uint8_t len, const uint8_t challenge[HMAC_SIZE], const uint8_t key[AES_KEY_SIZE], uint8_t hmac[HMAC_SIZE])
Create a 6-byte HMAC for authentication (proprietary IO-Homecontrol scheme).
bool aes128_decrypt(const uint8_t in[AES_BLOCK_SIZE], const uint8_t key[AES_KEY_SIZE], uint8_t out[AES_BLOCK_SIZE])
AES‑128 ECB decrypt a single 16‑byte block.
bool verify_hmac(const uint8_t *data, uint8_t len, const uint8_t hmac[HMAC_SIZE], const uint8_t challenge[HMAC_SIZE], const uint8_t key[AES_KEY_SIZE])
Verify a received HMAC using constant-time comparison.
void construct_iv(const uint8_t *data, uint8_t len, const uint8_t challenge[HMAC_SIZE], uint8_t iv[IV_SIZE])
Construct the 16-byte initialization vector (IV) for AES encryption.
bool aes128_encrypt(const uint8_t in[AES_BLOCK_SIZE], const uint8_t key[AES_KEY_SIZE], uint8_t out[AES_BLOCK_SIZE])
AES-128 ECB encrypt a single 16-byte block.
bool crypt_key(const uint8_t *data, uint8_t len, const uint8_t challenge[HMAC_SIZE], const uint8_t in[AES_KEY_SIZE], uint8_t out[AES_KEY_SIZE])
Encrypt or decrypt a system key during pairing.
static constexpr uint8_t AES_BLOCK_SIZE
AES block size.
Definition proto_frame.h:84
static constexpr uint8_t HMAC_SIZE
Authentication HMAC is 6 bytes (truncated AES output).
Definition proto_frame.h:82
static constexpr uint8_t AES_KEY_SIZE
AES-128 key size.
Definition proto_frame.h:83
static constexpr uint8_t IV_SIZE
Initialization vector size for AES.
Definition proto_frame.h:85
IO-Homecontrol 2W protocol definitions.