From 0a353eeb378b9ca5d49e23c7dfc175ae07107b08 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Wed, 24 Apr 2024 13:09:17 +0100 Subject: [PATCH] Improved XXTEA operations. Added XXTEA Decrypt. --- src/core/config/Categories.json | 5 +- src/core/lib/XXTEA.mjs | 174 +++++++++++++++++++++++++ src/core/operations/XXTEA.mjs | 182 --------------------------- src/core/operations/XXTEADecrypt.mjs | 57 +++++++++ src/core/operations/XXTEAEncrypt.mjs | 52 ++++++++ tests/operations/index.mjs | 1 + tests/operations/tests/XXTEA.mjs | 62 ++++----- 7 files changed, 308 insertions(+), 225 deletions(-) create mode 100644 src/core/lib/XXTEA.mjs delete mode 100644 src/core/operations/XXTEA.mjs create mode 100644 src/core/operations/XXTEADecrypt.mjs create mode 100644 src/core/operations/XXTEAEncrypt.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 134cae2d..bebdd6a5 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -117,6 +117,8 @@ "XOR Brute Force", "Vigenère Encode", "Vigenère Decode", + "XXTEA Encrypt", + "XXTEA Decrypt", "To Morse Code", "From Morse Code", "Bacon Cipher Encode", @@ -155,8 +157,7 @@ "Typex", "Lorenz", "Colossus", - "SIGABA", - "XXTEA" + "SIGABA" ] }, { diff --git a/src/core/lib/XXTEA.mjs b/src/core/lib/XXTEA.mjs new file mode 100644 index 00000000..6d556fc5 --- /dev/null +++ b/src/core/lib/XXTEA.mjs @@ -0,0 +1,174 @@ +/** + * XXTEA library + * + * Encryption Algorithm Authors: + * David J. Wheeler + * Roger M. Needham + * + * @author Ma Bingyao [mabingyao@gmail.com] + * @author n1474335 [n1474335@gmail.com] + * @license MIT + */ + +const DELTA = 0x9E3779B9; + +/** + * Convert a buffer to a Uint8Array + * @param {Uint32Array} v + * @param {boolean} includeLength + * @returns {Uint8Array} + */ +function toUint8Array(v, includeLength) { + const length = v.length; + let n = length << 2; + if (includeLength) { + const m = v[length - 1]; + n -= 4; + if ((m < n - 3) || (m > n)) { + return null; + } + n = m; + } + const bytes = new Uint8Array(n); + for (let i = 0; i < n; i++) { + bytes[i] = v[i >> 2] >> ((i & 3) << 3); + } + return bytes; +} + +/** + * Convert a buffer to a Uint32Array + * @param {TypedArray} bs + * @param {boolean} includeLength + * @returns {Uint32Array} + */ +function toUint32Array(bs, includeLength) { + const length = bs.length; + let n = length >> 2; + if ((length & 3) !== 0) { + ++n; + } + let v; + if (includeLength) { + v = new Uint32Array(n + 1); + v[n] = length; + } else { + v = new Uint32Array(n); + } + for (let i = 0; i < length; ++i) { + v[i >> 2] |= bs[i] << ((i & 3) << 3); + } + return v; +} + +/** + * Mask an int to 32 bits + * @param {number} i + * @returns {number} + */ +function int32(i) { + return i & 0xFFFFFFFF; +} + +/** + * MX function for data randomisation + * @param {number} sum + * @param {number} y + * @param {number} z + * @param {number} p + * @param {number} e + * @param {number} k + * @returns {number} + */ +function mx(sum, y, z, p, e, k) { + return ((z >>> 5 ^ y << 2) + (y >>> 3 ^ z << 4)) ^ ((sum ^ y) + (k[p & 3 ^ e] ^ z)); +} + +/** + * Ensure an array is a multiple of 16 bits + * @param {TypedArray} k + * @returns {TypedArray} + */ +function fixk(k) { + if (k.length < 16) { + const key = new Uint8Array(16); + key.set(k); + return key; + } + return k; +} + +/** + * Performs XXTEA encryption on a Uint32Array + * @param {Uint32Array} v + * @param {Uint32Array} k + * @returns {Uint32Array} + */ +function encryptUint32Array(v, k) { + const length = v.length; + const n = length - 1; + let y, z, sum, e, p, q; + z = v[n]; + sum = 0; + for (q = Math.floor(6 + 52 / length) | 0; q > 0; --q) { + sum = int32(sum + DELTA); + e = sum >>> 2 & 3; + for (p = 0; p < n; ++p) { + y = v[p + 1]; + z = v[p] = int32(v[p] + mx(sum, y, z, p, e, k)); + } + y = v[0]; + z = v[n] = int32(v[n] + mx(sum, y, z, n, e, k)); + } + return v; +} + +/** + * Performs XXTEA decryption on a Uint32Array + * @param {Uint32Array} v + * @param {Uint32Array} k + * @returns {Uint32Array} + */ +function decryptUint32Array(v, k) { + const length = v.length; + const n = length - 1; + let y, z, sum, e, p; + y = v[0]; + const q = Math.floor(6 + 52 / length); + for (sum = int32(q * DELTA); sum !== 0; sum = int32(sum - DELTA)) { + e = sum >>> 2 & 3; + for (p = n; p > 0; --p) { + z = v[p - 1]; + y = v[p] = int32(v[p] - mx(sum, y, z, p, e, k)); + } + z = v[n]; + y = v[0] = int32(v[0] - mx(sum, y, z, 0, e, k)); + } + return v; +} + +/** + * Encrypt function + * @param {TypedArray} data + * @param {TypedArray} key + * @returns {Uint8Array} + */ +export function encrypt(data, key) { + if (data === undefined || data === null || data.length === 0) { + return data; + } + return toUint8Array(encryptUint32Array(toUint32Array(data, true), toUint32Array(fixk(key), false)), false); +} + +/** + * Decrypt function + * @param {TypedArray} data + * @param {TypedArray} key + * @returns {Uint8Array} + */ +export function decrypt(data, key) { + if (data === undefined || data === null || data.length === 0) { + return data; + } + return toUint8Array(decryptUint32Array(toUint32Array(data, false), toUint32Array(fixk(key), false)), true); +} diff --git a/src/core/operations/XXTEA.mjs b/src/core/operations/XXTEA.mjs deleted file mode 100644 index 1a0a4368..00000000 --- a/src/core/operations/XXTEA.mjs +++ /dev/null @@ -1,182 +0,0 @@ -/** - * @author devcydo [devcydo@gmail.com] - * @author Ma Bingyao [mabingyao@gmail.com] - * @copyright Crown Copyright 2022 - * @license Apache-2.0 - */ - -import Operation from "../Operation.mjs"; -import OperationError from "../errors/OperationError.mjs"; -import {toBase64} from "../lib/Base64.mjs"; -import Utils from "../Utils.mjs"; - -/** - * XXTEA Encrypt operation - */ -class XXTEAEncrypt extends Operation { - - /** - * XXTEAEncrypt constructor - */ - constructor() { - super(); - - this.name = "XXTEA"; - this.module = "Default"; - this.description = "Corrected Block TEA (often referred to as XXTEA) is a block cipher designed to correct weaknesses in the original Block TEA. XXTEA operates on variable-length blocks that are some arbitrary multiple of 32 bits in size (minimum 64 bits). The number of full cycles depends on the block size, but there are at least six (rising to 32 for small block sizes). The original Block TEA applies the XTEA round function to each word in the block and combines it additively with its leftmost neighbour. Slow diffusion rate of the decryption process was immediately exploited to break the cipher. Corrected Block TEA uses a more involved round function which makes use of both immediate neighbours in processing each word in the block."; - this.infoURL = "https://wikipedia.org/wiki/XXTEA"; - this.inputType = "string"; - this.outputType = "string"; - this.args = [ - { - "name": "Key", - "type": "string", - "value": "", - }, - ]; - } - - /** - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - run(input, args) { - let key = args[0]; - - if (input === undefined || input === null || input.length === 0) { - throw new OperationError("Invalid input length (0)"); - } - - if (key === undefined || key === null || key.length === 0) { - throw new OperationError("Invalid key length (0)"); - } - - input = Utils.convertToByteString(input, "utf8"); - key = Utils.convertToByteString(key, "utf8"); - - input = this.convertToUint32Array(input, true); - key = this.fixLength(this.convertToUint32Array(key, false)); - - let encrypted = this.encryptUint32Array(input, key); - - encrypted = toBase64(this.toBinaryString(encrypted, false)); - - return encrypted; - } - - /** - * Convert Uint32Array to binary string - * - * @param {Uint32Array} v - * @param {Boolean} includeLength - * @returns {string} - */ - toBinaryString(v, includeLENGTH) { - const LENGTH = v.length; - let n = LENGTH << 2; - if (includeLENGTH) { - const M = v[LENGTH - 1]; - n -= 4; - if ((M < n - 3) || (M > n)) { - return null; - } - n = M; - } - for (let i = 0; i < LENGTH; i++) { - v[i] = String.fromCharCode( - v[i] & 0xFF, - v[i] >>> 8 & 0xFF, - v[i] >>> 16 & 0xFF, - v[i] >>> 24 & 0xFF - ); - } - const RESULT = v.join(""); - if (includeLENGTH) { - return RESULT.substring(0, n); - } - return RESULT; - } - - /** - * @param {number} sum - * @param {number} y - * @param {number} z - * @param {number} p - * @param {number} e - * @param {number} k - * @returns {number} - */ - mx(sum, y, z, p, e, k) { - return ((z >>> 5 ^ y << 2) + (y >>> 3 ^ z << 4)) ^ ((sum ^ y) + (k[p & 3 ^ e] ^ z)); - } - - - /** - * Encrypt Uint32Array - * - * @param {Uint32Array} v - * @param {number} k - * @returns {Uint32Array} - */ - encryptUint32Array(v, k) { - const LENGTH = v.length; - const N = LENGTH - 1; - let y, z, sum, e, p, q; - z = v[N]; - sum = 0; - for (q = Math.floor(6 + 52 / LENGTH) | 0; q > 0; --q) { - sum = (sum + 0x9E3779B9) & 0xFFFFFFFF; - e = sum >>> 2 & 3; - for (p = 0; p < N; ++p) { - y = v[p + 1]; - z = v[p] = (v[p] + this.mx(sum, y, z, p, e, k)) & 0xFFFFFFFF; - } - y = v[0]; - z = v[N] = (v[N] + this.mx(sum, y, z, N, e, k)) & 0xFFFFFFFF; - } - return v; - } - - /** - * Fixes the Uint32Array lenght to 4 - * - * @param {Uint32Array} k - * @returns {Uint32Array} - */ - fixLength(k) { - if (k.length < 4) { - k.length = 4; - } - return k; - } - - /** - * Convert string to Uint32Array - * - * @param {string} bs - * @param {Boolean} includeLength - * @returns {Uint32Array} - */ - convertToUint32Array(bs, includeLength) { - const LENGTH = bs.length; - let n = LENGTH >> 2; - if ((LENGTH & 3) !== 0) { - ++n; - } - let v; - if (includeLength) { - v = new Array(n + 1); - v[n] = LENGTH; - } else { - v = new Array(n); - } - for (let i = 0; i < LENGTH; ++i) { - v[i >> 2] |= bs.charCodeAt(i) << ((i & 3) << 3); - } - return v; - } - -} - -export default XXTEAEncrypt; diff --git a/src/core/operations/XXTEADecrypt.mjs b/src/core/operations/XXTEADecrypt.mjs new file mode 100644 index 00000000..496e5409 --- /dev/null +++ b/src/core/operations/XXTEADecrypt.mjs @@ -0,0 +1,57 @@ +/** + * @author devcydo [devcydo@gmail.com] + * @author Ma Bingyao [mabingyao@gmail.com] + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import {decrypt} from "../lib/XXTEA.mjs"; + +/** + * XXTEA Decrypt operation + */ +class XXTEADecrypt extends Operation { + + /** + * XXTEADecrypt constructor + */ + constructor() { + super(); + + this.name = "XXTEA Decrypt"; + this.module = "Ciphers"; + this.description = "Corrected Block TEA (often referred to as XXTEA) is a block cipher designed to correct weaknesses in the original Block TEA. XXTEA operates on variable-length blocks that are some arbitrary multiple of 32 bits in size (minimum 64 bits). The number of full cycles depends on the block size, but there are at least six (rising to 32 for small block sizes). The original Block TEA applies the XTEA round function to each word in the block and combines it additively with its leftmost neighbour. Slow diffusion rate of the decryption process was immediately exploited to break the cipher. Corrected Block TEA uses a more involved round function which makes use of both immediate neighbours in processing each word in the block."; + this.infoURL = "https://wikipedia.org/wiki/XXTEA"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = new Uint8Array(Utils.convertToByteArray(args[0].string, args[0].option)); + try { + return decrypt(new Uint8Array(input), key).buffer; + } catch (err) { + throw new OperationError("Unable to decrypt using this key"); + } + } + +} + +export default XXTEADecrypt; diff --git a/src/core/operations/XXTEAEncrypt.mjs b/src/core/operations/XXTEAEncrypt.mjs new file mode 100644 index 00000000..85379de0 --- /dev/null +++ b/src/core/operations/XXTEAEncrypt.mjs @@ -0,0 +1,52 @@ +/** + * @author devcydo [devcydo@gmail.com] + * @author Ma Bingyao [mabingyao@gmail.com] + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {encrypt} from "../lib/XXTEA.mjs"; + +/** + * XXTEA Encrypt operation + */ +class XXTEAEncrypt extends Operation { + + /** + * XXTEAEncrypt constructor + */ + constructor() { + super(); + + this.name = "XXTEA Encrypt"; + this.module = "Ciphers"; + this.description = "Corrected Block TEA (often referred to as XXTEA) is a block cipher designed to correct weaknesses in the original Block TEA. XXTEA operates on variable-length blocks that are some arbitrary multiple of 32 bits in size (minimum 64 bits). The number of full cycles depends on the block size, but there are at least six (rising to 32 for small block sizes). The original Block TEA applies the XTEA round function to each word in the block and combines it additively with its leftmost neighbour. Slow diffusion rate of the decryption process was immediately exploited to break the cipher. Corrected Block TEA uses a more involved round function which makes use of both immediate neighbours in processing each word in the block."; + this.infoURL = "https://wikipedia.org/wiki/XXTEA"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = new Uint8Array(Utils.convertToByteArray(args[0].string, args[0].option)); + return encrypt(new Uint8Array(input), key).buffer; + } + +} + +export default XXTEAEncrypt; diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index c931e8c6..40ce7a2e 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -153,6 +153,7 @@ import "./tests/UnescapeString.mjs"; import "./tests/Unicode.mjs"; import "./tests/YARA.mjs"; import "./tests/ParseCSR.mjs"; +import "./tests/XXTEA.mjs"; const testStatus = { allTestsPassing: true, diff --git a/tests/operations/tests/XXTEA.mjs b/tests/operations/tests/XXTEA.mjs index 4787f086..61e2c188 100644 --- a/tests/operations/tests/XXTEA.mjs +++ b/tests/operations/tests/XXTEA.mjs @@ -1,62 +1,42 @@ /** - * Base64 tests. + * XXTEA tests. * * @author devcydo [devcydo@gmail.com] - * - * @copyright Crown Copyright 2022 + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2024 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { - name: "XXTEA", + name: "XXTEA Encrypt and Decrypt", input: "Hello World! 你好,中国!", - expectedOutput: "QncB1C0rHQoZ1eRiPM4dsZtRi9pNrp7sqvX76cFXvrrIHXL6", - reecipeConfig: [ + expectedOutput: "Hello World! 你好,中国!", + recipeConfig: [ { - args: "1234567890" + "op": "XXTEA Encrypt", + "args": [{ "option": "UTF8", "string": "1234567890" }] }, + { + "op": "XXTEA Decrypt", + "args": [{ "option": "UTF8", "string": "1234567890" }] + } ], }, { - name: "XXTEA", + name: "XXTEA Encrypt", input: "ნუ პანიკას", - expectedOutput: "PbWjnbFmP8Apu2MKOGNbjeW/72IZLlLMS/g82ozLxwE=", - reecipeConfig: [ + expectedOutput: "3db5a39db1663fc029bb630a38635b8de5bfef62192e52cc4bf83cda8ccbc701", + recipeConfig: [ { - args: "1234567890" + "op": "XXTEA Encrypt", + "args": [{ "option": "UTF8", "string": "1234567890" }] }, - ], - }, - { - name: "XXTEA", - input: "ნუ პანიკას", - expectedOutput: "dHrOJ4ClIx6gH33NPSafYR2GG7UqsazY6Xfb0iekBY4=", - reecipeConfig: [ { - args: "ll3kj209d2" - }, + "op": "To Hex", + "args": ["None", 0] + } ], - }, - { - name: "XXTEA", - input: "", - expectedOutput: "Invalid input length (0)", - reecipeConfig: [ - { - args: "1234567890" - }, - ], - }, - { - name: "XXTEA", - input: "", - expectedOutput: "Invalid input length (0)", - reecipeConfig: [ - { - args: "" - }, - ], - }, + } ]);