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