|
Home IO Control
ESPHome add-on for IO-Homecontrol devices
|
Functions | |
| void | compute_checksum (uint8_t byte, uint8_t &c1, uint8_t &c2) |
| Proprietary checksum algorithm used in IV construction. | |
| static void | construct_iv_prefix (const uint8_t *data, uint8_t len, uint8_t iv[IV_SIZE]) |
| Shared core of construct_iv() (2W) and construct_iv_1w_sequence() (1W): fills IV bytes 0-9 — up to 8 bytes of span data (0x55-padded if the span is shorter), then the two running checksum bytes (compute_checksum()) accumulated over the whole span. | |
| 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. | |
| void | construct_iv_1w_sequence (const uint8_t *data, uint8_t len, uint16_t sequence, uint8_t iv[IV_SIZE]) |
| Construct the 16-byte IV for the 1W sequence-keyed HMAC (create_1w_hmac()). | |
| void | construct_iv_1w_node (const uint8_t node[NODE_ID_SIZE], uint8_t iv[IV_SIZE]) |
| Construct the 16-byte IV for the 1W key-wrap primitive from a sender's node address alone. | |
| 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 | 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 | 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 | create_1w_hmac (const uint8_t *data, uint8_t len, uint16_t sequence, const uint8_t controller_key[AES_KEY_SIZE], uint8_t hmac[HMAC_SIZE]) |
| Create the 6-byte authenticator for a 1W frame. | |
| 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. | |
| static bool | xor_with_encrypted_iv (const uint8_t iv[IV_SIZE], const uint8_t in[AES_KEY_SIZE], uint8_t out[AES_KEY_SIZE]) |
| Shared core of crypt_key() (2W) and crypt_1w_key() (1W): AES-128-ECB-encrypt iv under the public TRANSFER_KEY, then XOR the result over in. | |
| 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. | |
| bool | crypt_1w_key (const uint8_t node[NODE_ID_SIZE], const uint8_t in[AES_KEY_SIZE], uint8_t out[AES_KEY_SIZE]) |
| Encrypt or decrypt a 1W controller key during add-controller key adoption (CMD 0x30). | |
| void | generate_challenge (uint8_t out[HMAC_SIZE]) |
| Generate 6 random bytes for a challenge using the ESP32 hardware RNG. | |
| bool esphome::home_io_control::crypto::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.
| in | 16‑byte ciphertext block. |
| key | 16‑byte AES key. |
| out | Output: 16‑byte plaintext block. |
Definition at line 355 of file proto_crypto.cpp.
| bool esphome::home_io_control::crypto::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.
AES‑128 ECB encrypt a single 16‑byte block.
| in | 16‑byte plaintext block. |
| key | 16‑byte AES key. |
| out | Output: 16‑byte ciphertext block. |
Definition at line 351 of file proto_crypto.cpp.
| void esphome::home_io_control::crypto::compute_checksum | ( | uint8_t | byte, |
| uint8_t & | c1, | ||
| uint8_t & | c2 ) |
Proprietary checksum algorithm used in IV construction.
Update running checksum bytes (c1, c2) with a new data byte (IO‑Homecontrol IV derivation).
This is NOT a standard CRC - it's a custom accumulator used by the IO-Homecontrol protocol to mix frame data into the initialization vector for AES encryption.
| byte | Input data byte. |
| c1 | First checksum byte (inout). |
| c2 | Second checksum byte (inout). |
Definition at line 272 of file proto_crypto.cpp.
| void esphome::home_io_control::crypto::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.
Construct the 16‑byte IV for AES encryption from frame data and challenge.
Layout: [bytes 0-7: frame data or 0x55 padding] [bytes 8-9: checksums] [bytes 10-15: challenge] The IV binds the HMAC to both the frame content and the random challenge, preventing replay attacks. Bytes 0-9 are construct_iv_prefix()'s shared core; see construct_iv_1w_sequence() for the 1W sibling that reuses it with a different tail.
Layout: bytes 0–7 = up to 8 frame bytes padded with 0x55, bytes 8–9 = checksums, bytes 10–15 = challenge.
| data | Frame data bytes. |
| len | Number of data bytes. |
| challenge | 6‑byte challenge from the device. |
| iv | Output: 16‑byte IV. |
Definition at line 318 of file proto_crypto.cpp.
| void esphome::home_io_control::crypto::construct_iv_1w_node | ( | const uint8_t | node[NODE_ID_SIZE], |
| uint8_t | iv[IV_SIZE] ) |
Construct the 16-byte IV for the 1W key-wrap primitive from a sender's node address alone.
Construct the 16‑byte IV for the 1W key‑wrap primitive (crypt_1w_key()) from a sender's node address alone.
A 1W broadcast (CMD_ONEWAY_ADD_CONTROLLER, 0x30) has no per-exchange challenge to bind to — the node address is the only public, per-sender input available, so it is repeated to fill the block. node[i % NODE_ID_SIZE] naturally lands iv[15] on node[0] (15 % 3 == 0), matching the published vector without a separate last-byte special case.
Layout: the 3‑byte node address repeated through byte 14 ({N0,N1,N2,N0,N1,N2,…}), with iv[15] = N0. Unlike construct_iv() (2W), this IV carries no challenge or frame data — a 1W broadcast has no per‑exchange challenge to bind to, so the sender's node address (already public: it is the frame header's plaintext source field) is the only per‑sender distinguishing input available.
| node | 3‑byte sender node address. |
| iv | Output: 16‑byte IV. |
Definition at line 345 of file proto_crypto.cpp.
| void esphome::home_io_control::crypto::construct_iv_1w_sequence | ( | const uint8_t * | data, |
| uint8_t | len, | ||
| uint16_t | sequence, | ||
| uint8_t | iv[IV_SIZE] ) |
Construct the 16-byte IV for the 1W sequence-keyed HMAC (create_1w_hmac()).
Construct the 16‑byte IV for the 1W sequence‑keyed HMAC (create_1w_hmac()).
Layout: [bytes 0-7: span data or 0x55 padding] [bytes 8-9: checksums] [bytes 10-11: sequence, big-endian] [bytes 12-15: 0x55 padding]. Shares construct_iv_prefix() with the 2W construct_iv() — only the tail differs, matching a real 1W frame's shape: there is no per-exchange challenge, only the sequence number being transmitted. Verified against the published IV vector in tests/corpus/captures/oneway/reference_1w_oneway_execute_iv_vector.yaml (payload 00 01 43 D2 00 00 00, sequence 0x0599 -> IV 000143D2000000550500059955555555).
Layout: bytes 0–7 = up to 8 span bytes padded with 0x55, bytes 8–9 = checksums (the same shared core construct_iv() uses), bytes 10–11 = the 2‑byte sequence (big‑endian), bytes 12–15 = 0x55 padding. A 1W frame has no per‑exchange challenge to bind to — the transmitted sequence is the only per‑transmission value available, so it takes the challenge's place in the tail while the padded bytes fill the rest.
| data | Span bytes to authenticate. The span is command‑specific — see create_1w_hmac(). |
| len | Number of span bytes. |
| sequence | 2‑byte rolling sequence for this transmission. |
| iv | Output: 16‑byte IV. |
Definition at line 330 of file proto_crypto.cpp.
|
static |
Shared core of construct_iv() (2W) and construct_iv_1w_sequence() (1W): fills IV bytes 0-9 — up to 8 bytes of span data (0x55-padded if the span is shorter), then the two running checksum bytes (compute_checksum()) accumulated over the whole span.
The two callers differ only in how they fill the remaining tail (bytes 10-15): a 6-byte challenge for 2W, or a 2-byte sequence plus 0x55 padding for 1W. Factoring this out keeps the checksum/pad accumulation in exactly one place rather than forked per variant — the same reasoning that keeps xor_with_encrypted_iv() (below) shared between crypt_key() and crypt_1w_key().
Definition at line 297 of file proto_crypto.cpp.
| bool esphome::home_io_control::crypto::create_1w_hmac | ( | const uint8_t * | data, |
| uint8_t | len, | ||
| uint16_t | sequence, | ||
| const uint8_t | controller_key[AES_KEY_SIZE], | ||
| uint8_t | hmac[HMAC_SIZE] ) |
Create the 6-byte authenticator for a 1W frame.
Create a 6‑byte HMAC for a 1W (one‑way) transmission — the sequence‑keyed sibling of create_hmac().
See proto_crypto.h for the full contract — in particular that data/len is a command-specific span the caller must get right, and that controller_key is whichever key the controller identity holds. Structurally identical to create_hmac() apart from the IV tail (sequence instead of challenge), so both truncate the same AES-128 output the same way.
Process: build the IV from [span + sequence] (construct_iv_1w_sequence()) → AES‑128‑ECB encrypt with the controller's key → take the first 6 bytes.
| data | Command‑specific span bytes to authenticate (see |
| len | Number of span bytes. |
| sequence | 2‑byte rolling sequence for this transmission. |
| controller_key | 16‑byte key held by the transmitting controller identity. This may be the hub's own system_key, or a foreign key adopted from another network (Phase 3A "key adoption"); the primitive is key‑agnostic and simply uses whichever key the caller supplies. |
| hmac | Output: 6‑byte HMAC. |
Definition at line 378 of file proto_crypto.cpp.
| bool esphome::home_io_control::crypto::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).
Create a 6‑byte HMAC for authentication (IO‑Homecontrol proprietary scheme).
See proto_crypto.h for full parameter documentation and construction details.
Process: build IV from [data + challenge] → AES‑128‑ECB encrypt IV with system key → take first 6 bytes. This is NOT a standard HMAC; it is specific to IO‑Homecontrol and matches the protocol specification as implemented in compatible devices.
| data | Frame data bytes (usually command + payload). |
| len | Length of data. |
| challenge | 6‑byte random challenge. |
| key | 16‑byte system key. |
| hmac | Output: 6‑byte HMAC. |
Definition at line 361 of file proto_crypto.cpp.
| bool esphome::home_io_control::crypto::crypt_1w_key | ( | const uint8_t | node[NODE_ID_SIZE], |
| const uint8_t | in[AES_KEY_SIZE], | ||
| uint8_t | out[AES_KEY_SIZE] ) |
Encrypt or decrypt a 1W controller key during add-controller key adoption (CMD 0x30).
See proto_crypto.h for the full contract; this is crypt_key()'s 1W sibling, sharing the same xor_with_encrypted_iv() core with a node-derived IV in place of a challenge-derived one.
Shares its core with crypt_key() — XOR with AES‑128‑ECB(IV, TRANSFER_KEY) — and differs only in IV construction: construct_iv_1w_node() derives the IV from the sender's node address alone, in place of crypt_key()'s frame-data-and-challenge IV. As with crypt_key(), the operation is its own inverse (XOR with the same keystream both ways), so there is no direction flag: the same call encrypts a plaintext key for transmission or decrypts an overheard ciphertext.
| node | 3‑byte sender node address (from the frame header, plaintext). |
| in | Input key (plaintext to encrypt, or ciphertext to decrypt). |
| out | Output key. |
Definition at line 435 of file proto_crypto.cpp.
| bool esphome::home_io_control::crypto::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.
Encrypt or decrypt the system key during pairing (XOR with AES‑encrypted IV).
The system key is XORed with AES(IV, TRANSFER_KEY) - the same operation encrypts and decrypts. The TRANSFER_KEY is a hardcoded key known to all IO-Homecontrol devices.
The same operation works for both directions: encrypting for the device and decrypting the device's acknowledgement.
| data | Frame data (typically the key‑init command byte). |
| len | Length of data (usually 1). |
| challenge | Device's 6‑byte challenge. |
| in | Input key (plaintext for encrypt, ciphertext for decrypt). |
| out | Output key. |
Definition at line 425 of file proto_crypto.cpp.
| void esphome::home_io_control::crypto::generate_challenge | ( | uint8_t | out[HMAC_SIZE] | ) |
Generate 6 random bytes for a challenge using the ESP32 hardware RNG.
Generate 6 random bytes for a challenge using the ESP hardware RNG.
| out | Output buffer (6 bytes). |
Definition at line 442 of file proto_crypto.cpp.
| bool esphome::home_io_control::crypto::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.
Verify a received 6‑byte HMAC using constant‑time comparison.
Full documentation in proto_crypto.h.
| data | Frame data bytes. |
| len | Length of data. |
| hmac | Received 6‑byte HMAC. |
| challenge | Challenge used in HMAC calculation. |
| key | 16‑byte system key. |
Definition at line 392 of file proto_crypto.cpp.
|
static |
Shared core of crypt_key() (2W) and crypt_1w_key() (1W): AES-128-ECB-encrypt iv under the public TRANSFER_KEY, then XOR the result over in.
The two callers differ only in how they build iv (construct_iv() vs. construct_iv_1w_node()); factoring this tail out keeps the encrypt-under-TRANSFER_KEY-then-XOR logic in exactly one place instead of two copies that could drift apart.
Definition at line 412 of file proto_crypto.cpp.