milenage
Version:
3GPP authentication and key generation functions (MILENAGE)
265 lines (185 loc) • 7.28 kB
JavaScript
const crypto = require('crypto');
module.exports = function Milenage(params) {
function op_c() {
const cipher = crypto.createCipheriv('aes-128-ecb', params.key, Buffer.alloc(0));
return params.op_c || computeOpc(cipher);
}
/*-------------------------------------------------------------------
* Algorithm f1
*-------------------------------------------------------------------
*
* Computes network authentication code MAC-A from key K, random
* challenge RAND, sequence number SQN and authentication management
* field AMF.
*
*-----------------------------------------------------------------*/
function f1(rand, sqn, amf) {
const cipher = crypto.createCipheriv('aes-128-ecb', params.key, Buffer.alloc(0));
const op_c = params.op_c || computeOpc(cipher);
const rijndaelInput = new Uint8Array(16);
for (let i = 0; i < 16; i++) // @todo unroll?
rijndaelInput[i] = rand[i] ^ op_c[i];
const temp = Uint8Array.from(cipher.update(rijndaelInput));
const in1 = new Uint8Array(16);
for (let i = 0; i < 6; i++) { // @todo unroll?
in1[i] = sqn[i];
in1[i + 8] = sqn[i];
}
for (let i = 0; i < 2; i++) { // @todo unroll?
in1[i + 6] = amf[i];
in1[i + 14] = amf[i];
}
/* XOR op_c and in1, rotate by r1=64, and XOR *
* on the constant c1 (which is all zeroes) */
for (let i = 0; i < 16; i++)
rijndaelInput[(i + 8) % 16] = in1[i] ^ op_c[i];
/* XOR on the value temp computed before */
for (let i = 0; i < 16; i++)
rijndaelInput[i] ^= temp[i];
const out1 = Uint8Array.from(cipher.update(rijndaelInput));
for (let i = 0; i < 16; i++)
out1[i] ^= op_c[i];
const mac_a = new Uint8Array(8);
for (let i = 0; i < 8; i++)
mac_a[i] = out1[i];
return { op_c, mac_a };
}
/*-------------------------------------------------------------------
* Algorithms f2-f5
*-------------------------------------------------------------------
*
* Takes key K and random challenge RAND, and returns response RES,
* confidentiality key CK, integrity key IK and anonymity key AK.
*
*-----------------------------------------------------------------*/
function f2345(rand) {
const cipher = crypto.createCipheriv('aes-128-ecb', params.key, Buffer.alloc(0));
const op_c = params.op_c || computeOpc(cipher);
const rijndaelInput = new Uint8Array(16);
for (let i = 0; i < 16; i++)
rijndaelInput[i] = rand[i] ^ op_c[i];
const temp = Uint8Array.from(cipher.update(rijndaelInput));
/* To obtain output block OUT2: XOR OPc and TEMP, *
* rotate by r2=0, and XOR on the constant c2 (which *
* is all zeroes except that the last bit is 1). */
for (let i = 0; i < 16; i++)
rijndaelInput[i] = temp[i] ^ op_c[i];
rijndaelInput[15] ^= 1;
let out = Uint8Array.from(cipher.update(rijndaelInput));
for (let i = 0; i < 16; i++)
out[i] ^= op_c[i];
const res = new Uint8Array(8);
for (let i = 0; i < 8; i++)
res[i] = out[i + 8];
const ak = new Uint8Array(6);
for (let i = 0; i < 6; i++)
ak[i] = out[i];
/* To obtain output block OUT3: XOR OPc and TEMP, *
* rotate by r3=32, and XOR on the constant c3 (which *
* is all zeroes except that the next to last bit is 1). */
for (let i = 0; i < 16; i++)
rijndaelInput[(i + 12) % 16] = temp[i] ^ op_c[i];
rijndaelInput[15] ^= 2;
out = Uint8Array.from(cipher.update(rijndaelInput));
for (let i = 0; i < 16; i++)
out[i] ^= op_c[i];
const ck = new Uint8Array(16);
for (let i = 0; i < 16; i++)
ck[i] = out[i];
/* To obtain output block OUT4: XOR OPc and TEMP, *
* rotate by r4=64, and XOR on the constant c4 (which *
* is all zeroes except that the 2nd from last bit is 1). */
for (let i = 0; i < 16; i++)
rijndaelInput[(i + 8) % 16] = temp[i] ^ op_c[i];
rijndaelInput[15] ^= 4;
out = Uint8Array.from(cipher.update(rijndaelInput));
for (let i = 0; i < 16; i++)
out[i] ^= op_c[i];
const ik = new Uint8Array(16);
for (let i = 0; i < 16; i++)
ik[i] = out[i];
return { op_c, res, ck, ik, ak };
}
/*-------------------------------------------------------------------
* Algorithm f1*
*-------------------------------------------------------------------
*
* Computes resynch authentication code MAC-S from key K, random
* challenge RAND, sequence number SQN and authentication management
* field AMF.
*
*-----------------------------------------------------------------*/
function f1star(rand, sqn, amf) {
const cipher = crypto.createCipheriv('aes-128-ecb', params.key, Buffer.alloc(0));
const op_c = params.op_c || computeOpc(cipher);
const rijndaelInput = new Uint8Array(16);
for (let i = 0; i < 16; i++)
rijndaelInput[i] = rand[i] ^ op_c[i];
const temp = Uint8Array.from(cipher.update(rijndaelInput));
const in1 = new Uint8Array(16);
for (let i = 0; i < 6; i++) {
in1[i] = sqn[i];
in1[i + 8] = sqn[i];
}
for (let i = 0; i < 2; i++) {
in1[i + 6] = amf[i];
in1[i + 14] = amf[i];
}
/* XOR op_c and in1, rotate by r1=64, and XOR *
* on the constant c1 (which is all zeroes) */
for (let i = 0; i < 16; i++)
rijndaelInput[(i + 8) % 16] = in1[i] ^ op_c[i];
/* XOR on the value temp computed before */
for (let i = 0; i < 16; i++)
rijndaelInput[i] ^= temp[i];
const out1 = Uint8Array.from(cipher.update(rijndaelInput));
for (let i = 0; i < 16; i++)
out1[i] ^= op_c[i];
const mac_s = new Uint8Array(8);
for (let i = 0; i < 8; i++)
mac_s[i] = out1[i + 8];
return { op_c, mac_s };
}
/*-------------------------------------------------------------------
* Algorithm f5*
*-------------------------------------------------------------------
*
* Takes key K and random challenge RAND, and returns resynch
* anonymity key AK.
*
*-----------------------------------------------------------------*/
function f5star(rand) {
const cipher = crypto.createCipheriv('aes-128-ecb', params.key, Buffer.alloc(0));
const op_c = params.op_c || computeOpc(cipher);
const rijndaelInput = new Uint8Array(16);
for (let i = 0; i < 16; i++)
rijndaelInput[i] = rand[i] ^ op_c[i];
const temp = Uint8Array.from(cipher.update(rijndaelInput));
/* To obtain output block OUT5: XOR OPc and TEMP, *
* rotate by r5=96, and XOR on the constant c5 (which *
* is all zeroes except that the 3rd from last bit is 1). */
for (let i = 0; i < 16; i++)
rijndaelInput[(i + 4) % 16] = temp[i] ^ op_c[i];
rijndaelInput[15] ^= 8;
const out = Uint8Array.from(cipher.update(rijndaelInput));
for (let i = 0; i < 16; i++)
out[i] ^= op_c[i];
const ak_s = new Uint8Array(8);
for (let i = 0; i < 6; i++)
ak_s[i] = out[i];
return { op_c, ak_s };
}
function computeOpc(cipher) {
const op_c = Uint8Array.from(cipher.update(params.op));
for (let i = 0; i < 16; i++)
op_c[i] ^= params.op[i];
return op_c;
}
return {
op_c,
f1,
f2345,
f1star,
f5star
};
};