diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json
index 43d5dc4e..79f3c19c 100644
--- a/src/core/config/Categories.json
+++ b/src/core/config/Categories.json
@@ -365,6 +365,7 @@
"Compare SSDEEP hashes",
"Compare CTPH hashes",
"HMAC",
+ "CMAC",
"Bcrypt",
"Bcrypt compare",
"Bcrypt parse",
diff --git a/src/core/operations/CMAC.mjs b/src/core/operations/CMAC.mjs
new file mode 100644
index 00000000..8c365362
--- /dev/null
+++ b/src/core/operations/CMAC.mjs
@@ -0,0 +1,141 @@
+/**
+ * @author mikecat
+ * @copyright Crown Copyright 2022
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+import Utils from "../Utils.mjs";
+import forge from "node-forge";
+import { toHexFast } from "../lib/Hex.mjs";
+import OperationError from "../errors/OperationError.mjs";
+
+/**
+ * CMAC operation
+ */
+class CMAC extends Operation {
+
+ /**
+ * CMAC constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "CMAC";
+ this.module = "Crypto";
+ this.description = "CMAC is a block-cipher based message authentication code algorithm.
RFC4493 defines AES-CMAC that uses AES encryption with a 128-bit key.
NIST SP 800-38B suggests usages of AES with other key lengths and Triple DES.";
+ this.infoURL = "https://wikipedia.org/wiki/CMAC";
+ this.inputType = "ArrayBuffer";
+ this.outputType = "string";
+ this.args = [
+ {
+ "name": "Key",
+ "type": "toggleString",
+ "value": "",
+ "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
+ },
+ {
+ "name": "Encryption algorithm",
+ "type": "option",
+ "value": ["AES", "Triple DES"]
+ }
+ ];
+ }
+
+ /**
+ * @param {ArrayBuffer} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ const key = Utils.convertToByteString(args[0].string, args[0].option);
+ const info = (function() {
+ switch (args[1]) {
+ case "AES":
+ if (key.length !== 16 && key.length !== 24 && key.length !== 32) {
+ throw new OperationError("the key for AES must be either 16, 24, or 32 bytes (currently " + key.length + " bytes)");
+ }
+ return {
+ "algorithm": "AES-ECB",
+ "blockSize": 16,
+ "Rb": new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x87]),
+ };
+ case "Triple DES":
+ if (key.length !== 24) {
+ throw new OperationError("the key for Triple DES must be 24 bytes (currently " + key.length + " bytes)");
+ }
+ return {
+ "algorithm": "3DES-ECB",
+ "blockSize": 8,
+ "Rb": new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0x1b]),
+ };
+ default:
+ throw new OperationError("undefined encryption algorithm");
+ }
+ })();
+ const xor = function(a, b, out) {
+ if (!out) out = new Uint8Array(a.length);
+ for (let i = 0; i < a.length; i++) {
+ out[i] = a[i] ^ b[i];
+ }
+ return out;
+ };
+ const leftShift1 = function(a) {
+ const out = new Uint8Array(a.length);
+ let carry = 0;
+ for (let i = a.length - 1; i >= 0; i--) {
+ out[i] = (a[i] << 1) | carry;
+ carry = a[i] >> 7;
+ }
+ return out;
+ };
+ const cipher = forge.cipher.createCipher(info.algorithm, key);
+ const encrypt = function(a, out) {
+ if (!out) out = new Uint8Array(a.length);
+ cipher.start();
+ cipher.update(forge.util.createBuffer(a));
+ cipher.finish();
+ const cipherText = cipher.output.getBytes();
+ for (let i = 0; i < a.length; i++) {
+ out[i] = cipherText.charCodeAt(i);
+ }
+ return out;
+ };
+
+ const L = encrypt(new Uint8Array(info.blockSize));
+ const K1 = leftShift1(L);
+ if (L[0] & 0x80) xor(K1, info.Rb, K1);
+ const K2 = leftShift1(K1);
+ if (K1[0] & 0x80) xor(K2, info.Rb, K2);
+
+ const n = Math.ceil(input.byteLength / info.blockSize);
+ const lastBlock = (function() {
+ if (n === 0) {
+ const data = new Uint8Array(K2);
+ data[0] ^= 0x80;
+ return data;
+ }
+ const inputLast = new Uint8Array(input, info.blockSize * (n - 1));
+ if (inputLast.length === info.blockSize) {
+ return xor(inputLast, K1, inputLast);
+ } else {
+ const data = new Uint8Array(info.blockSize);
+ data.set(inputLast, 0);
+ data[inputLast.length] = 0x80;
+ return xor(data, K2, data);
+ }
+ })();
+ const X = new Uint8Array(info.blockSize);
+ const Y = new Uint8Array(info.blockSize);
+ for (let i = 0; i < n - 1; i++) {
+ xor(X, new Uint8Array(input, info.blockSize * i, info.blockSize), Y);
+ encrypt(Y, X);
+ }
+ xor(lastBlock, X, Y);
+ const T = encrypt(Y);
+ return toHexFast(T);
+ }
+
+}
+
+export default CMAC;
diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs
index 19e70970..18a1b59f 100644
--- a/tests/operations/index.mjs
+++ b/tests/operations/index.mjs
@@ -124,6 +124,7 @@ import "./tests/UnescapeString.mjs";
import "./tests/LS47.mjs";
import "./tests/LZString.mjs";
import "./tests/NTLM.mjs";
+import "./tests/CMAC.mjs";
// Cannot test operations that use the File type yet
// import "./tests/SplitColourChannels.mjs";
diff --git a/tests/operations/tests/CMAC.mjs b/tests/operations/tests/CMAC.mjs
new file mode 100644
index 00000000..e5e3b40b
--- /dev/null
+++ b/tests/operations/tests/CMAC.mjs
@@ -0,0 +1,314 @@
+/**
+ * @author mikecat
+ * @copyright Crown Copyright 2022
+ * @license Apache-2.0
+ */
+import TestRegister from "../../lib/TestRegister.mjs";
+
+// values in "NIST's CSRC" testcases are taken from here:
+// https://csrc.nist.gov/projects/cryptographic-standards-and-guidelines/example-values
+
+TestRegister.addTests([
+ {
+ "name": "CMAC-AES128 NIST's CSRC Example #1",
+ "input": "",
+ "expectedOutput": "bb1d6929e95937287fa37d129b756746",
+ "recipeConfig": [
+ {
+ "op": "CMAC",
+ "args": [{"option": "Hex", "string": "2b7e151628aed2a6abf7158809cf4f3c"}, "AES"]
+ },
+ ]
+ },
+ {
+ "name": "CMAC-AES128 NIST's CSRC Example #2",
+ "input": "6bc1bee22e409f96e93d7e117393172a",
+ "expectedOutput": "070a16b46b4d4144f79bdd9dd04a287c",
+ "recipeConfig": [
+ {
+ "op": "From Hex",
+ "args": ["None"]
+ },
+ {
+ "op": "CMAC",
+ "args": [{"option": "Hex", "string": "2b7e151628aed2a6abf7158809cf4f3c"}, "AES"]
+ },
+ ]
+ },
+ {
+ "name": "CMAC-AES128 NIST's CSRC Example #3",
+ "input": "6bc1bee22e409f96e93d7e117393172aae2d8a57",
+ "expectedOutput": "7d85449ea6ea19c823a7bf78837dfade",
+ "recipeConfig": [
+ {
+ "op": "From Hex",
+ "args": ["None"]
+ },
+ {
+ "op": "CMAC",
+ "args": [{"option": "Hex", "string": "2b7e151628aed2a6abf7158809cf4f3c"}, "AES"]
+ },
+ ]
+ },
+ {
+ "name": "CMAC-AES128 NIST's CSRC Example #4",
+ "input": "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710",
+ "expectedOutput": "51f0bebf7e3b9d92fc49741779363cfe",
+ "recipeConfig": [
+ {
+ "op": "From Hex",
+ "args": ["None"]
+ },
+ {
+ "op": "CMAC",
+ "args": [{"option": "Hex", "string": "2b7e151628aed2a6abf7158809cf4f3c"}, "AES"]
+ },
+ ]
+ },
+ {
+ "name": "CMAC-AES192 NIST's CSRC Example #1",
+ "input": "",
+ "expectedOutput": "d17ddf46adaacde531cac483de7a9367",
+ "recipeConfig": [
+ {
+ "op": "CMAC",
+ "args": [{"option": "Hex", "string": "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b"}, "AES"]
+ },
+ ]
+ },
+ {
+ "name": "CMAC-AES192 NIST's CSRC Example #2",
+ "input": "6bc1bee22e409f96e93d7e117393172a",
+ "expectedOutput": "9e99a7bf31e710900662f65e617c5184",
+ "recipeConfig": [
+ {
+ "op": "From Hex",
+ "args": ["None"]
+ },
+ {
+ "op": "CMAC",
+ "args": [{"option": "Hex", "string": "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b"}, "AES"]
+ },
+ ]
+ },
+ {
+ "name": "CMAC-AES192 NIST's CSRC Example #3",
+ "input": "6bc1bee22e409f96e93d7e117393172aae2d8a57",
+ "expectedOutput": "3d75c194ed96070444a9fa7ec740ecf8",
+ "recipeConfig": [
+ {
+ "op": "From Hex",
+ "args": ["None"]
+ },
+ {
+ "op": "CMAC",
+ "args": [{"option": "Hex", "string": "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b"}, "AES"]
+ },
+ ]
+ },
+ {
+ "name": "CMAC-AES192 NIST's CSRC Example #4",
+ "input": "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710",
+ "expectedOutput": "a1d5df0eed790f794d77589659f39a11",
+ "recipeConfig": [
+ {
+ "op": "From Hex",
+ "args": ["None"]
+ },
+ {
+ "op": "CMAC",
+ "args": [{"option": "Hex", "string": "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b"}, "AES"]
+ },
+ ]
+ },
+ {
+ "name": "CMAC-AES256 NIST's CSRC Example #1",
+ "input": "",
+ "expectedOutput": "028962f61b7bf89efc6b551f4667d983",
+ "recipeConfig": [
+ {
+ "op": "CMAC",
+ "args": [{"option": "Hex", "string": "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"}, "AES"]
+ },
+ ]
+ },
+ {
+ "name": "CMAC-AES256 NIST's CSRC Example #2",
+ "input": "6bc1bee22e409f96e93d7e117393172a",
+ "expectedOutput": "28a7023f452e8f82bd4bf28d8c37c35c",
+ "recipeConfig": [
+ {
+ "op": "From Hex",
+ "args": ["None"]
+ },
+ {
+ "op": "CMAC",
+ "args": [{"option": "Hex", "string": "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"}, "AES"]
+ },
+ ]
+ },
+ {
+ "name": "CMAC-AES256 NIST's CSRC Example #3",
+ "input": "6bc1bee22e409f96e93d7e117393172aae2d8a57",
+ "expectedOutput": "156727dc0878944a023c1fe03bad6d93",
+ "recipeConfig": [
+ {
+ "op": "From Hex",
+ "args": ["None"]
+ },
+ {
+ "op": "CMAC",
+ "args": [{"option": "Hex", "string": "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"}, "AES"]
+ },
+ ]
+ },
+ {
+ "name": "CMAC-AES256 NIST's CSRC Example #4",
+ "input": "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710",
+ "expectedOutput": "e1992190549f6ed5696a2c056c315410",
+ "recipeConfig": [
+ {
+ "op": "From Hex",
+ "args": ["None"]
+ },
+ {
+ "op": "CMAC",
+ "args": [{"option": "Hex", "string": "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"}, "AES"]
+ },
+ ]
+ },
+ {
+ "name": "CMAC-TDES (1) NIST's CSRC Sample #1",
+ "input": "",
+ "expectedOutput": "7db0d37df936c550",
+ "recipeConfig": [
+ {
+ "op": "CMAC",
+ "args": [{"option": "Hex", "string": "0123456789abcdef23456789abcdef01456789abcdef0123"}, "Triple DES"]
+ },
+ ]
+ },
+ {
+ "name": "CMAC-TDES (1) NIST's CSRC Sample #2",
+ "input": "6bc1bee22e409f96e93d7e117393172a",
+ "expectedOutput": "30239cf1f52e6609",
+ "recipeConfig": [
+ {
+ "op": "From Hex",
+ "args": ["None"]
+ },
+ {
+ "op": "CMAC",
+ "args": [{"option": "Hex", "string": "0123456789abcdef23456789abcdef01456789abcdef0123"}, "Triple DES"]
+ },
+ ]
+ },
+ {
+ "name": "CMAC-TDES (1) NIST's CSRC Sample #3",
+ "input": "6bc1bee22e409f96e93d7e117393172aae2d8a57",
+ "expectedOutput": "6c9f3ee4923f6be2",
+ "recipeConfig": [
+ {
+ "op": "From Hex",
+ "args": ["None"]
+ },
+ {
+ "op": "CMAC",
+ "args": [{"option": "Hex", "string": "0123456789abcdef23456789abcdef01456789abcdef0123"}, "Triple DES"]
+ },
+ ]
+ },
+ {
+ "name": "CMAC-TDES (1) NIST's CSRC Sample #4",
+ "input": "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51",
+ "expectedOutput": "99429bd0bf7904e5",
+ "recipeConfig": [
+ {
+ "op": "From Hex",
+ "args": ["None"]
+ },
+ {
+ "op": "CMAC",
+ "args": [{"option": "Hex", "string": "0123456789abcdef23456789abcdef01456789abcdef0123"}, "Triple DES"]
+ },
+ ]
+ },
+ {
+ "name": "CMAC-TDES (2) NIST's CSRC Sample #1",
+ "input": "",
+ "expectedOutput": "79ce52a7f786a960",
+ "recipeConfig": [
+ {
+ "op": "CMAC",
+ "args": [{"option": "Hex", "string": "0123456789abcdef23456789abcdef010123456789abcdef"}, "Triple DES"]
+ },
+ ]
+ },
+ {
+ "name": "CMAC-TDES (2) NIST's CSRC Sample #2",
+ "input": "6bc1bee22e409f96e93d7e117393172a",
+ "expectedOutput": "cc18a0b79af2413b",
+ "recipeConfig": [
+ {
+ "op": "From Hex",
+ "args": ["None"]
+ },
+ {
+ "op": "CMAC",
+ "args": [{"option": "Hex", "string": "0123456789abcdef23456789abcdef010123456789abcdef"}, "Triple DES"]
+ },
+ ]
+ },
+ {
+ "name": "CMAC-TDES (2) NIST's CSRC Sample #3",
+ "input": "6bc1bee22e409f96e93d7e117393172aae2d8a57",
+ "expectedOutput": "c06d377ecd101969",
+ "recipeConfig": [
+ {
+ "op": "From Hex",
+ "args": ["None"]
+ },
+ {
+ "op": "CMAC",
+ "args": [{"option": "Hex", "string": "0123456789abcdef23456789abcdef010123456789abcdef"}, "Triple DES"]
+ },
+ ]
+ },
+ {
+ "name": "CMAC-TDES (2) NIST's CSRC Sample #4",
+ "input": "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51",
+ "expectedOutput": "9cd33580f9b64dfb",
+ "recipeConfig": [
+ {
+ "op": "From Hex",
+ "args": ["None"]
+ },
+ {
+ "op": "CMAC",
+ "args": [{"option": "Hex", "string": "0123456789abcdef23456789abcdef010123456789abcdef"}, "Triple DES"]
+ },
+ ]
+ },
+ {
+ "name": "CMAC-AES: invalid key length",
+ "input": "",
+ "expectedOutput": "the key for AES must be either 16, 24, or 32 bytes (currently 20 bytes)",
+ "recipeConfig": [
+ {
+ "op": "CMAC",
+ "args": [{"option": "Hex", "string": "00112233445566778899aabbccddeeff01234567"}, "AES"]
+ },
+ ]
+ },
+ {
+ "name": "CMAC-TDES: invalid key length",
+ "input": "",
+ "expectedOutput": "the key for Triple DES must be 24 bytes (currently 20 bytes)",
+ "recipeConfig": [
+ {
+ "op": "CMAC",
+ "args": [{"option": "Hex", "string": "00112233445566778899aabbccddeeff01234567"}, "Triple DES"]
+ },
+ ]
+ },
+]);