multi-downloader-nx/modules/cmac.ts
2023-12-24 19:29:01 -08:00

114 lines
No EOL
3.2 KiB
TypeScript

//Originally from https://github.com/Frooastside/node-widevine/blob/main/src/cmac.ts
import crypto from 'crypto';
export class AES_CMAC {
private readonly BLOCK_SIZE = 16;
private readonly XOR_RIGHT = Buffer.from([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87]);
private readonly EMPTY_BLOCK_SIZE_BUFFER = Buffer.alloc(this.BLOCK_SIZE);
private _key: Buffer;
private _subkeys: { first: Buffer; second: Buffer };
public constructor(key: Buffer) {
if (![16, 24, 32].includes(key.length)) {
throw new Error('Key size must be 128, 192, or 256 bits.');
}
this._key = key;
this._subkeys = this._generateSubkeys();
}
public calculate(message: Buffer): Buffer {
const blockCount = this._getBlockCount(message);
let x = this.EMPTY_BLOCK_SIZE_BUFFER;
let y;
for (let i = 0; i < blockCount - 1; i++) {
const from = i * this.BLOCK_SIZE;
const block = message.subarray(from, from + this.BLOCK_SIZE);
y = this._xor(x, block);
x = this._aes(y);
}
y = this._xor(x, this._getLastBlock(message));
x = this._aes(y);
return x;
}
private _generateSubkeys(): { first: Buffer; second: Buffer } {
const l = this._aes(this.EMPTY_BLOCK_SIZE_BUFFER);
let first = this._bitShiftLeft(l);
if (l[0] & 0x80) {
first = this._xor(first, this.XOR_RIGHT);
}
let second = this._bitShiftLeft(first);
if (first[0] & 0x80) {
second = this._xor(second, this.XOR_RIGHT);
}
return { first: first, second: second };
}
private _getBlockCount(message: Buffer): number {
const blockCount = Math.ceil(message.length / this.BLOCK_SIZE);
return blockCount === 0 ? 1 : blockCount;
}
private _aes(message: Buffer): Buffer {
const cipher = crypto.createCipheriv(`aes-${this._key.length * 8}-cbc`, this._key, Buffer.alloc(this.BLOCK_SIZE));
const result = cipher.update(message).subarray(0, 16);
cipher.destroy();
return result;
}
private _getLastBlock(message: Buffer): Buffer {
const blockCount = this._getBlockCount(message);
const paddedBlock = this._padding(message, blockCount - 1);
let complete = false;
if (message.length > 0) {
complete = message.length % this.BLOCK_SIZE === 0;
}
const key = complete ? this._subkeys.first : this._subkeys.second;
return this._xor(paddedBlock, key);
}
private _padding(message: Buffer, blockIndex: number): Buffer {
const block = Buffer.alloc(this.BLOCK_SIZE);
const from = blockIndex * this.BLOCK_SIZE;
const slice = message.subarray(from, from + this.BLOCK_SIZE);
block.set(slice);
if (slice.length !== this.BLOCK_SIZE) {
block[slice.length] = 0x80;
}
return block;
}
private _bitShiftLeft(input: Buffer): Buffer {
const output = Buffer.alloc(input.length);
let overflow = 0;
for (let i = input.length - 1; i >= 0; i--) {
output[i] = (input[i] << 1) | overflow;
overflow = input[i] & 0x80 ? 1 : 0;
}
return output;
}
private _xor(a: Buffer, b: Buffer): Buffer {
const length = Math.min(a.length, b.length);
const output = Buffer.alloc(length);
for (let i = 0; i < length; i++) {
output[i] = a[i] ^ b[i];
}
return output;
}
}