Skip to content

Commit 705f765

Browse files
authored
fix: AES-128-MMO incorrect calculation (#1292)
1 parent 801212f commit 705f765

File tree

2 files changed

+27
-21
lines changed

2 files changed

+27
-21
lines changed

src/zspec/utils.ts

+21-21
Original file line numberDiff line numberDiff line change
@@ -185,37 +185,37 @@ export function crc16CCITTFALSE(data: number[] | Uint8Array | Buffer): number {
185185
return calcCRC(data, 16, 0x1021, 0xffff);
186186
}
187187

188+
function aes128MmoHashUpdate(result: Buffer, data: Buffer, dataSize: number): void {
189+
while (dataSize >= AES_MMO_128_BLOCK_SIZE) {
190+
const cipher = createCipheriv('aes-128-ecb', result, null);
191+
const block = data.subarray(0, AES_MMO_128_BLOCK_SIZE);
192+
const encryptedBlock = Buffer.concat([cipher.update(block), cipher.final()]);
193+
194+
// XOR encrypted and plaintext
195+
for (let i = 0; i < AES_MMO_128_BLOCK_SIZE; i++) {
196+
result[i] = encryptedBlock[i] ^ block[i];
197+
}
198+
199+
data = data.subarray(AES_MMO_128_BLOCK_SIZE);
200+
dataSize -= AES_MMO_128_BLOCK_SIZE;
201+
}
202+
}
203+
188204
/**
189205
* AES-128-MMO (Matyas-Meyer-Oseas) hashing (using node 'crypto' built-in with 'aes-128-ecb')
190206
*
191207
* Used for Install Codes - see Document 13-0402-13 - 10.1
192208
*/
193209
export function aes128MmoHash(data: Buffer): Buffer {
194-
const update = (result: Buffer, data: Buffer, dataSize: number): boolean => {
195-
while (dataSize >= AES_MMO_128_BLOCK_SIZE) {
196-
const cipher = createCipheriv('aes-128-ecb', result, null);
197-
const block = data.subarray(0, AES_MMO_128_BLOCK_SIZE);
198-
const encryptedBlock = Buffer.concat([cipher.update(block), cipher.final()]);
199-
200-
// XOR encrypted and plaintext
201-
for (let i = 0; i < AES_MMO_128_BLOCK_SIZE; i++) {
202-
result[i] = encryptedBlock[i] ^ block[i];
203-
}
204-
205-
data = data.subarray(AES_MMO_128_BLOCK_SIZE);
206-
dataSize -= AES_MMO_128_BLOCK_SIZE;
207-
}
208-
209-
return true;
210-
};
211-
212210
const hashResult = Buffer.alloc(AES_MMO_128_BLOCK_SIZE);
213211
const temp = Buffer.alloc(AES_MMO_128_BLOCK_SIZE);
214212
let remainingLength = data.length;
215213
let position = 0;
216214

217215
for (position; remainingLength >= AES_MMO_128_BLOCK_SIZE; ) {
218-
update(hashResult, data.subarray(position, position + AES_MMO_128_BLOCK_SIZE), data.length);
216+
const chunk = data.subarray(position, position + AES_MMO_128_BLOCK_SIZE);
217+
218+
aes128MmoHashUpdate(hashResult, chunk, chunk.length);
219219

220220
position += AES_MMO_128_BLOCK_SIZE;
221221
remainingLength -= AES_MMO_128_BLOCK_SIZE;
@@ -230,14 +230,14 @@ export function aes128MmoHash(data: Buffer): Buffer {
230230

231231
// if appending the bit string will push us beyond the 16-byte boundary, hash that block and append another 16-byte block
232232
if (AES_MMO_128_BLOCK_SIZE - remainingLength < 3) {
233-
update(hashResult, temp, AES_MMO_128_BLOCK_SIZE);
233+
aes128MmoHashUpdate(hashResult, temp, AES_MMO_128_BLOCK_SIZE);
234234
temp.fill(0);
235235
}
236236

237237
temp[AES_MMO_128_BLOCK_SIZE - 2] = (data.length >> 5) & 0xff;
238238
temp[AES_MMO_128_BLOCK_SIZE - 1] = (data.length << 3) & 0xff;
239239

240-
update(hashResult, temp, AES_MMO_128_BLOCK_SIZE);
240+
aes128MmoHashUpdate(hashResult, temp, AES_MMO_128_BLOCK_SIZE);
241241

242242
const result = Buffer.alloc(AES_MMO_128_BLOCK_SIZE);
243243

test/zspec/utils.test.ts

+6
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,15 @@ describe('ZSpec Utils', () => {
5454
const val1 = Buffer.from('83FED3407A939723A5C639FF4C12', 'hex');
5555
// Example from Zigbee spec
5656
const val2 = Buffer.from('83FED3407A939723A5C639B26916D505C3B5', 'hex');
57+
// Example from Zigbee spec C.6.1
58+
const val3 = Buffer.from('76777475727370717E7F7C7D7A7B7879C0', 'hex');
59+
// Example from Zigbee spec C.6.1
60+
const val4 = Buffer.from('1C1D1E1F18191A1B14151617101112133C3D537529A7A9A03F669DCD886CB52C', 'hex');
5761

5862
expect(ZSpec.Utils.aes128MmoHash(val1)).toStrictEqual(Buffer.from('58C1828CF7F1C3FE29E7B1024AD84BFA', 'hex'));
5963
expect(ZSpec.Utils.aes128MmoHash(val2)).toStrictEqual(Buffer.from('66B6900981E1EE3CA4206B6B861C02BB', 'hex'));
64+
expect(ZSpec.Utils.aes128MmoHash(val3)).toStrictEqual(Buffer.from('3C3D537529A7A9A03F669DCD886CB52C', 'hex'));
65+
expect(ZSpec.Utils.aes128MmoHash(val4)).toStrictEqual(Buffer.from('4512807BF94CB3400F0E2C25FB76E999', 'hex'));
6066
});
6167

6268
it('Checks install codes of all lengths', () => {

0 commit comments

Comments
 (0)