-
Notifications
You must be signed in to change notification settings - Fork 18
/
cmac.js
118 lines (94 loc) · 3.52 KB
/
cmac.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
export class AES_CMAC {
BLOCK_SIZE = 16;
XOR_RIGHT = new Uint8Array([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87
]);
EMPTY_BLOCK_SIZE_BUFFER = new Uint8Array(this.BLOCK_SIZE);
constructor(key) {
if (![16, 24, 32].includes(key.length)) {
throw new Error("Key size must be 128, 192, or 256 bits.");
}
this._key = key;
}
async calculate(message) {
this._subkeys = await this._generateSubkeys();
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 = await this._aes(y);
}
y = this._xor(x, this._getLastBlock(message));
x = await this._aes(y);
return x;
}
async _generateSubkeys() {
const l = await 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 };
}
_getBlockCount(message) {
const blockCount = Math.ceil(message.length / this.BLOCK_SIZE);
return blockCount === 0 ? 1 : blockCount;
}
async _aes(message) {
const keyBuffer = forge.util.createBuffer(this._key, 'raw');
const cipher = forge.cipher.createCipher('AES-CBC', keyBuffer);
const iv = forge.util.createBuffer(new Uint8Array(16), 'raw');
cipher.start({
iv: iv.getBytes()
});
cipher.update(forge.util.createBuffer(message));
cipher.finish();
const outputBuffer = cipher.output;
return new Uint8Array(outputBuffer.getBytes().slice(0, 16).split('').map(c => c.charCodeAt(0)));
}
_getLastBlock(message) {
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);
}
_padding(message, blockIndex) {
const block = new Uint8Array(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;
}
_bitShiftLeft(input) {
const output = new Uint8Array(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;
}
_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;
}
}