Zigbee Install Code Converter
Derive Link Key from an Install Code via AES-MMO
HTML
<script src="https://cdn.rawgit.com/ricmoo/aes-js/e27b99df/index.js"></script>
<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body>
<h1>Zigbee Install Code Converter</h1>
<textarea id="input" placeholder="Install Code"></textarea>
<p>
Link Key: <span id="output"></span>
</p>
<input type="button" value="Convert" onclick="convert()" />
</body>
</html>
CSS
body {
font-family: sans-serif;
}
JavaScript
const BLOCKSIZE = 16;
function convert() {
let input = document.getElementById("input").value;
let hash = calculateHash(extractInstallCode(input));
document.getElementById("output").innerHTML = aesjs.utils.hex.fromBytes(hash);
}
// Based on https://github.com/Koenkk/zigbee-herdsman/blob/15e9af9f28284d8596a5af277167ed5d2d4e071d/src/controller/controller.ts#L214
function extractInstallCode(installCode) {
const aqaraMatch = installCode.match(/^G\$M:.+\$A:(.+)\$I:(.+)$/);
let key;
if (aqaraMatch) {
key = aqaraMatch[2];
}
else if (installCode.length === 95 || installCode.length === 91) {
const keyStart = installCode.length - (installCode.length === 95 ? 36 : 32);
key = installCode.substring(keyStart, installCode.length);
}
else {
key = installCode;
}
return aesjs.utils.hex.toBytes(key);
}
// AES-MMO Implementation based on https://github.com/zsmartsystems/com.zsmartsystems.zigbee/blob/master/com.zsmartsystems.zigbee/src/main/java/com/zsmartsystems/zigbee/security/MmoHash.java
function calculateHash(input) {
let output = new Uint8Array(BLOCKSIZE);
let cipher_in = new Uint8Array(BLOCKSIZE);
let inputCnt = 0;
let outputCnt = 0;
while (inputCnt < input.length) {
cipher_in[outputCnt++] = input[inputCnt++];
if (outputCnt >= BLOCKSIZE) {
output = doBlock(output, cipher_in);
outputCnt = 0;
}
}
cipher_in[outputCnt++] = 0x80;
while (outputCnt != BLOCKSIZE - 2) {
if (outputCnt >= BLOCKSIZE) {
output = doBlock(output, cipher_in);
outputCnt = 0;
}
cipher_in[outputCnt++] = 0x00;
}
cipher_in[outputCnt++] = ((input.length * 8) >> 8);
cipher_in[outputCnt] = ((input.length * 8) >> 0);
return doBlock(output, cipher_in);
}
function doBlock(keyBytes, inBytes) {
let aesEcb = new aesjs.ModeOfOperation.ecb(keyBytes);
let output = aesEcb.encrypt(inBytes);
for (let i = 0; i < BLOCKSIZE; i++) {
output[i] ^= inBytes[i];
}
return output;
}