aes-cmac
Version:
AES-CMAC implementation in typescript
152 lines (148 loc) • 4.43 kB
JavaScript
'use strict';
class BufferTools {
static bitShiftLeft(input) {
const output = new Uint8Array(input.length);
let overflow = 0;
for (let i = input.length - 1; i >= 0; i--) {
const byte = input[i];
output[i] = byte << 1 | overflow;
overflow = byte & 128 ? 1 : 0;
}
return output;
}
static xor(a, b) {
const length = Math.min(a.length, b.length);
const output = new Uint8Array(length);
for (let i = 0; i < length; i++) {
output[i] = a[i] ^ b[i];
}
return output;
}
static toBinaryString(input) {
let binary = "";
for (let i = 0; i < input.length; i++) {
for (let b = 7; b >= 0; b--) {
const byte = input[i];
binary += byte & 1 << b ? "1" : "0";
}
}
return binary;
}
}
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
class AesCmac {
constructor(key) {
__publicField(this, "blockSize", 16);
__publicField(this, "supportedLengths", [16, 24, 32]);
__publicField(this, "subkeys");
__publicField(this, "key");
if (key instanceof Uint8Array === false) {
throw new Error("The key must be provided as a Uint8Array.");
}
if (this.supportedLengths.includes(key.length) === false) {
throw new Error("Key size must be 128, 192, or 256 bits.");
}
this.key = crypto.subtle.importKey("raw", key, "AES-CBC", false, ["encrypt"]);
}
async generateSubkeys() {
const rb = Uint8Array.from([
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
135
]);
const z = new Uint8Array(this.blockSize);
const l = await this.aes(z);
let key1 = BufferTools.bitShiftLeft(l);
if (l[0] & 128) {
key1 = BufferTools.xor(key1, rb);
}
let key2 = BufferTools.bitShiftLeft(key1);
if (key1[0] & 128) {
key2 = BufferTools.xor(key2, rb);
}
return { key1, key2 };
}
async getSubKeys() {
if (!this.subkeys) {
this.subkeys = await this.generateSubkeys();
}
const key1 = new Uint8Array(this.blockSize);
key1.set(this.subkeys.key1);
const key2 = new Uint8Array(this.blockSize);
key2.set(this.subkeys.key2);
return { key1, key2 };
}
async calculate(message) {
if (message instanceof Uint8Array === false) {
throw new Error("The message must be provided as a Uint8Array.");
}
const blockCount = this.getBlockCount(message);
let x = new Uint8Array(this.blockSize);
let y = new Uint8Array(0);
for (let i = 0; i < blockCount - 1; i++) {
const from = i * this.blockSize;
const block = message.slice(from, from + this.blockSize);
y = BufferTools.xor(x, block);
x = await this.aes(y);
}
y = BufferTools.xor(x, await this.getLastBlock(message));
x = await this.aes(y);
return x;
}
getBlockCount(message) {
const blockCount = Math.ceil(message.length / this.blockSize);
return blockCount === 0 ? 1 : blockCount;
}
async aes(message) {
const iv = new Uint8Array(this.blockSize);
if ("then" in this.key) {
this.key = await this.key;
}
const aesCiphertext = await crypto.subtle.encrypt(
{ name: "AES-CBC", iv },
this.key,
message
);
return new Uint8Array(aesCiphertext.slice(0, 16));
}
async getLastBlock(message) {
if (!this.subkeys) {
this.subkeys = await this.generateSubkeys();
}
const blockCount = this.getBlockCount(message);
const paddedBlock = this.padding(message, blockCount - 1);
let complete = false;
if (message.length > 0) {
complete = message.length % this.blockSize === 0;
}
const key = complete ? this.subkeys.key1 : this.subkeys.key2;
return BufferTools.xor(paddedBlock, key);
}
padding(message, blockIndex) {
const block = new Uint8Array(this.blockSize);
const from = blockIndex * this.blockSize;
const slice = message.slice(from, from + this.blockSize);
block.set(slice);
if (slice.length !== this.blockSize) {
block[slice.length] = 128;
}
return block;
}
}
exports.AesCmac = AesCmac;
//# sourceMappingURL=aes-cmac.cjs.map