CyberChef/src/core/operations/CMAC.mjs

150 lines
5.0 KiB
JavaScript

/**
* @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.<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.";
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 algo = args[1];
const info = (function() {
switch (algo) {
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",
"key": key,
"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 !== 16 && key.length !== 24) {
throw new OperationError("The key for Triple DES must be 16 or 24 bytes (currently " + key.length + " bytes)");
}
return {
"algorithm": "3DES-ECB",
"key": key.length === 16 ? key + key.substring(0, 8) : key,
"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, info.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;