Skip to content

Commit 0fb3743

Browse files
committed
2 parents a19aea1 + fe9eb08 commit 0fb3743

File tree

4 files changed

+459
-0
lines changed

4 files changed

+459
-0
lines changed

src/core/config/Categories.json

+1
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,7 @@
368368
"Compare SSDEEP hashes",
369369
"Compare CTPH hashes",
370370
"HMAC",
371+
"CMAC",
371372
"Bcrypt",
372373
"Bcrypt compare",
373374
"Bcrypt parse",

src/core/operations/CMAC.mjs

+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/**
2+
* @author mikecat
3+
* @copyright Crown Copyright 2022
4+
* @license Apache-2.0
5+
*/
6+
7+
import Operation from "../Operation.mjs";
8+
import Utils from "../Utils.mjs";
9+
import forge from "node-forge";
10+
import { toHexFast } from "../lib/Hex.mjs";
11+
import OperationError from "../errors/OperationError.mjs";
12+
13+
/**
14+
* CMAC operation
15+
*/
16+
class CMAC extends Operation {
17+
18+
/**
19+
* CMAC constructor
20+
*/
21+
constructor() {
22+
super();
23+
24+
this.name = "CMAC";
25+
this.module = "Crypto";
26+
this.description = "CMAC is a block-cipher based message authentication code algorithm.<br><br>RFC4493 defines AES-CMAC that uses AES encryption with a 128-bit key.<br>NIST SP 800-38B suggests usages of AES with other key lengths and Triple DES.";
27+
this.infoURL = "https://wikipedia.org/wiki/CMAC";
28+
this.inputType = "ArrayBuffer";
29+
this.outputType = "string";
30+
this.args = [
31+
{
32+
"name": "Key",
33+
"type": "toggleString",
34+
"value": "",
35+
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
36+
},
37+
{
38+
"name": "Encryption algorithm",
39+
"type": "option",
40+
"value": ["AES", "Triple DES"]
41+
}
42+
];
43+
}
44+
45+
/**
46+
* @param {ArrayBuffer} input
47+
* @param {Object[]} args
48+
* @returns {string}
49+
*/
50+
run(input, args) {
51+
const key = Utils.convertToByteString(args[0].string, args[0].option);
52+
const info = (function() {
53+
switch (args[1]) {
54+
case "AES":
55+
if (key.length !== 16 && key.length !== 24 && key.length !== 32) {
56+
throw new OperationError("the key for AES must be either 16, 24, or 32 bytes (currently " + key.length + " bytes)");
57+
}
58+
return {
59+
"algorithm": "AES-ECB",
60+
"key": key,
61+
"blockSize": 16,
62+
"Rb": new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x87]),
63+
};
64+
case "Triple DES":
65+
if (key.length !== 16 && key.length !== 24) {
66+
throw new OperationError("the key for Triple DES must be 16 or 24 bytes (currently " + key.length + " bytes)");
67+
}
68+
return {
69+
"algorithm": "3DES-ECB",
70+
"key": key.length === 16 ? key + key.substring(0, 8) : key,
71+
"blockSize": 8,
72+
"Rb": new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0x1b]),
73+
};
74+
default:
75+
throw new OperationError("undefined encryption algorithm");
76+
}
77+
})();
78+
const xor = function(a, b, out) {
79+
if (!out) out = new Uint8Array(a.length);
80+
for (let i = 0; i < a.length; i++) {
81+
out[i] = a[i] ^ b[i];
82+
}
83+
return out;
84+
};
85+
const leftShift1 = function(a) {
86+
const out = new Uint8Array(a.length);
87+
let carry = 0;
88+
for (let i = a.length - 1; i >= 0; i--) {
89+
out[i] = (a[i] << 1) | carry;
90+
carry = a[i] >> 7;
91+
}
92+
return out;
93+
};
94+
const cipher = forge.cipher.createCipher(info.algorithm, info.key);
95+
const encrypt = function(a, out) {
96+
if (!out) out = new Uint8Array(a.length);
97+
cipher.start();
98+
cipher.update(forge.util.createBuffer(a));
99+
cipher.finish();
100+
const cipherText = cipher.output.getBytes();
101+
for (let i = 0; i < a.length; i++) {
102+
out[i] = cipherText.charCodeAt(i);
103+
}
104+
return out;
105+
};
106+
107+
const L = encrypt(new Uint8Array(info.blockSize));
108+
const K1 = leftShift1(L);
109+
if (L[0] & 0x80) xor(K1, info.Rb, K1);
110+
const K2 = leftShift1(K1);
111+
if (K1[0] & 0x80) xor(K2, info.Rb, K2);
112+
113+
const n = Math.ceil(input.byteLength / info.blockSize);
114+
const lastBlock = (function() {
115+
if (n === 0) {
116+
const data = new Uint8Array(K2);
117+
data[0] ^= 0x80;
118+
return data;
119+
}
120+
const inputLast = new Uint8Array(input, info.blockSize * (n - 1));
121+
if (inputLast.length === info.blockSize) {
122+
return xor(inputLast, K1, inputLast);
123+
} else {
124+
const data = new Uint8Array(info.blockSize);
125+
data.set(inputLast, 0);
126+
data[inputLast.length] = 0x80;
127+
return xor(data, K2, data);
128+
}
129+
})();
130+
const X = new Uint8Array(info.blockSize);
131+
const Y = new Uint8Array(info.blockSize);
132+
for (let i = 0; i < n - 1; i++) {
133+
xor(X, new Uint8Array(input, info.blockSize * i, info.blockSize), Y);
134+
encrypt(Y, X);
135+
}
136+
xor(lastBlock, X, Y);
137+
const T = encrypt(Y);
138+
return toHexFast(T);
139+
}
140+
141+
}
142+
143+
export default CMAC;

tests/operations/index.mjs

+1
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ import "./tests/LZString.mjs";
126126
import "./tests/NTLM.mjs";
127127
import "./tests/Shuffle.mjs";
128128
import "./tests/FletcherChecksum.mjs";
129+
import "./tests/CMAC.mjs";
129130

130131
// Cannot test operations that use the File type yet
131132
// import "./tests/SplitColourChannels.mjs";

0 commit comments

Comments
 (0)