From 705f7650a92a75b60168287458c49d4a127a971d Mon Sep 17 00:00:00 2001 From: Nerivec <62446222+Nerivec@users.noreply.github.com> Date: Sun, 12 Jan 2025 14:11:37 +0100 Subject: [PATCH] fix: AES-128-MMO incorrect calculation (#1292) --- src/zspec/utils.ts | 42 ++++++++++++++++++++-------------------- test/zspec/utils.test.ts | 6 ++++++ 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/src/zspec/utils.ts b/src/zspec/utils.ts index b0a0fc4238..b1bf76df78 100644 --- a/src/zspec/utils.ts +++ b/src/zspec/utils.ts @@ -185,37 +185,37 @@ export function crc16CCITTFALSE(data: number[] | Uint8Array | Buffer): number { return calcCRC(data, 16, 0x1021, 0xffff); } +function aes128MmoHashUpdate(result: Buffer, data: Buffer, dataSize: number): void { + while (dataSize >= AES_MMO_128_BLOCK_SIZE) { + const cipher = createCipheriv('aes-128-ecb', result, null); + const block = data.subarray(0, AES_MMO_128_BLOCK_SIZE); + const encryptedBlock = Buffer.concat([cipher.update(block), cipher.final()]); + + // XOR encrypted and plaintext + for (let i = 0; i < AES_MMO_128_BLOCK_SIZE; i++) { + result[i] = encryptedBlock[i] ^ block[i]; + } + + data = data.subarray(AES_MMO_128_BLOCK_SIZE); + dataSize -= AES_MMO_128_BLOCK_SIZE; + } +} + /** * AES-128-MMO (Matyas-Meyer-Oseas) hashing (using node 'crypto' built-in with 'aes-128-ecb') * * Used for Install Codes - see Document 13-0402-13 - 10.1 */ export function aes128MmoHash(data: Buffer): Buffer { - const update = (result: Buffer, data: Buffer, dataSize: number): boolean => { - while (dataSize >= AES_MMO_128_BLOCK_SIZE) { - const cipher = createCipheriv('aes-128-ecb', result, null); - const block = data.subarray(0, AES_MMO_128_BLOCK_SIZE); - const encryptedBlock = Buffer.concat([cipher.update(block), cipher.final()]); - - // XOR encrypted and plaintext - for (let i = 0; i < AES_MMO_128_BLOCK_SIZE; i++) { - result[i] = encryptedBlock[i] ^ block[i]; - } - - data = data.subarray(AES_MMO_128_BLOCK_SIZE); - dataSize -= AES_MMO_128_BLOCK_SIZE; - } - - return true; - }; - const hashResult = Buffer.alloc(AES_MMO_128_BLOCK_SIZE); const temp = Buffer.alloc(AES_MMO_128_BLOCK_SIZE); let remainingLength = data.length; let position = 0; for (position; remainingLength >= AES_MMO_128_BLOCK_SIZE; ) { - update(hashResult, data.subarray(position, position + AES_MMO_128_BLOCK_SIZE), data.length); + const chunk = data.subarray(position, position + AES_MMO_128_BLOCK_SIZE); + + aes128MmoHashUpdate(hashResult, chunk, chunk.length); position += AES_MMO_128_BLOCK_SIZE; remainingLength -= AES_MMO_128_BLOCK_SIZE; @@ -230,14 +230,14 @@ export function aes128MmoHash(data: Buffer): Buffer { // if appending the bit string will push us beyond the 16-byte boundary, hash that block and append another 16-byte block if (AES_MMO_128_BLOCK_SIZE - remainingLength < 3) { - update(hashResult, temp, AES_MMO_128_BLOCK_SIZE); + aes128MmoHashUpdate(hashResult, temp, AES_MMO_128_BLOCK_SIZE); temp.fill(0); } temp[AES_MMO_128_BLOCK_SIZE - 2] = (data.length >> 5) & 0xff; temp[AES_MMO_128_BLOCK_SIZE - 1] = (data.length << 3) & 0xff; - update(hashResult, temp, AES_MMO_128_BLOCK_SIZE); + aes128MmoHashUpdate(hashResult, temp, AES_MMO_128_BLOCK_SIZE); const result = Buffer.alloc(AES_MMO_128_BLOCK_SIZE); diff --git a/test/zspec/utils.test.ts b/test/zspec/utils.test.ts index b58c754559..50f27d751c 100644 --- a/test/zspec/utils.test.ts +++ b/test/zspec/utils.test.ts @@ -54,9 +54,15 @@ describe('ZSpec Utils', () => { const val1 = Buffer.from('83FED3407A939723A5C639FF4C12', 'hex'); // Example from Zigbee spec const val2 = Buffer.from('83FED3407A939723A5C639B26916D505C3B5', 'hex'); + // Example from Zigbee spec C.6.1 + const val3 = Buffer.from('76777475727370717E7F7C7D7A7B7879C0', 'hex'); + // Example from Zigbee spec C.6.1 + const val4 = Buffer.from('1C1D1E1F18191A1B14151617101112133C3D537529A7A9A03F669DCD886CB52C', 'hex'); expect(ZSpec.Utils.aes128MmoHash(val1)).toStrictEqual(Buffer.from('58C1828CF7F1C3FE29E7B1024AD84BFA', 'hex')); expect(ZSpec.Utils.aes128MmoHash(val2)).toStrictEqual(Buffer.from('66B6900981E1EE3CA4206B6B861C02BB', 'hex')); + expect(ZSpec.Utils.aes128MmoHash(val3)).toStrictEqual(Buffer.from('3C3D537529A7A9A03F669DCD886CB52C', 'hex')); + expect(ZSpec.Utils.aes128MmoHash(val4)).toStrictEqual(Buffer.from('4512807BF94CB3400F0E2C25FB76E999', 'hex')); }); it('Checks install codes of all lengths', () => {