diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 459b3071..58e70571 100755 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -98,6 +98,8 @@ "Rail Fence Cipher Encode", "Rail Fence Cipher Decode", "Atbash Cipher", + "CipherSaber2 Encrypt", + "CipherSaber2 Decrypt", "Substitute", "Derive PBKDF2 key", "Derive EVP key", diff --git a/src/core/lib/CipherSaber2.mjs b/src/core/lib/CipherSaber2.mjs new file mode 100644 index 00000000..bf3954e9 --- /dev/null +++ b/src/core/lib/CipherSaber2.mjs @@ -0,0 +1,34 @@ +/** + * @author n1073645 [n1073645@gmail.com] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ +export function encode(tempIVP, key, rounds, input) { + const ivp = new Uint8Array(key.concat(tempIVP)); + const state = new Array(256).fill(0); + let j = 0, i = 0; + const result = []; + + // Mixing states based off of IV. + for (let i = 0; i < 256; i++) + state[i] = i; + const ivpLength = ivp.length; + for (let r = 0; r < rounds; r ++) { + for (let k = 0; k < 256; k++) { + j = (j + state[k] + ivp[k % ivpLength]) % 256; + [state[k], state[j]] = [state[j], state[k]]; + } + } + j = 0; + i = 0; + + // XOR cipher with key. + for (let x = 0; x < input.length; x++) { + i = (++i) % 256; + j = (j + state[i]) % 256; + [state[i], state[j]] = [state[j], state[i]]; + const n = (state[i] + state[j]) % 256; + result.push(state[n] ^ input[x]); + } + return result; +} diff --git a/src/core/operations/CipherSaber2Decrypt.mjs b/src/core/operations/CipherSaber2Decrypt.mjs new file mode 100644 index 00000000..53d61468 --- /dev/null +++ b/src/core/operations/CipherSaber2Decrypt.mjs @@ -0,0 +1,61 @@ +/** + * @author n1073645 [n1073645@gmail.com] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import { encode } from "../lib/CipherSaber2.mjs"; +import Utils from "../Utils.mjs"; + +/** + * CipherSaber2 Decrypt operation + */ +class CipherSaber2Decrypt extends Operation { + + /** + * CipherSaber2Decrypt constructor + */ + constructor() { + super(); + + this.name = "CipherSaber2 Decrypt"; + this.module = "Crypto"; + this.description = "CipherSaber is a simple symmetric encryption protocol based on the RC4 stream cipher. It gives reasonably strong protection of message confidentiality, yet it's designed to be simple enough that even novice programmers can memorize the algorithm and implement it from scratch."; + this.infoURL = "https://wikipedia.org/wiki/CipherSaber"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + name: "Key", + type: "toggleString", + value: "", + toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + name: "Rounds", + type: "number", + value: 20 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + input = new Uint8Array(input); + const result = [], + key = Utils.convertToByteArray(args[0].string, args[0].option), + rounds = args[1]; + + const tempIVP = input.slice(0, 10); + input = input.slice(10); + return new Uint8Array(result.concat(encode(tempIVP, key, rounds, input))).buffer; + } + +} + +export default CipherSaber2Decrypt; diff --git a/src/core/operations/CipherSaber2Encrypt.mjs b/src/core/operations/CipherSaber2Encrypt.mjs new file mode 100644 index 00000000..dd86bd52 --- /dev/null +++ b/src/core/operations/CipherSaber2Encrypt.mjs @@ -0,0 +1,65 @@ +/** + * @author n1073645 [n1073645@gmail.com] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import crypto from "crypto"; +import { encode } from "../lib/CipherSaber2.mjs"; +import Utils from "../Utils.mjs"; + +/** + * CipherSaber2 Encrypt operation + */ +class CipherSaber2Encrypt extends Operation { + + /** + * CipherSaber2Encrypt constructor + */ + constructor() { + super(); + + this.name = "CipherSaber2 Encrypt"; + this.module = "Crypto"; + this.description = "CipherSaber is a simple symmetric encryption protocol based on the RC4 stream cipher. It gives reasonably strong protection of message confidentiality, yet it's designed to be simple enough that even novice programmers can memorize the algorithm and implement it from scratch."; + this.infoURL = "https://wikipedia.org/wiki/CipherSaber"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + name: "Key", + type: "toggleString", + value: "", + toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + name: "Rounds", + type: "number", + value: 20 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + input = new Uint8Array(input); + const result = [], + key = Utils.convertToByteArray(args[0].string, args[0].option), + rounds = args[1]; + + // Assign into initialisation vector based on cipher mode. + const tempIVP = crypto.randomBytes(10); + for (let m = 0; m < 10; m++) + result.push(tempIVP[m]); + + return new Uint8Array(result.concat(encode(tempIVP, key, rounds, input))).buffer; + } + +} + +export default CipherSaber2Encrypt; diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index 44f57b07..7cf68055 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -98,6 +98,7 @@ import "./tests/ParseUDP.mjs"; import "./tests/AvroToJSON.mjs"; import "./tests/Lorenz.mjs"; import "./tests/LuhnChecksum.mjs"; +import "./tests/CipherSaber2.mjs"; // Cannot test operations that use the File type yet diff --git a/tests/operations/tests/CipherSaber2.mjs b/tests/operations/tests/CipherSaber2.mjs new file mode 100644 index 00000000..dd675d45 --- /dev/null +++ b/tests/operations/tests/CipherSaber2.mjs @@ -0,0 +1,45 @@ +/** + * Ciphersaber2 tests. + * + * @author n1073645 [n1073645@gmail.com] + * + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "CipherSaber2 Encrypt", + input: "Hello World", + expectedMatch: /.{21}/s, + recipeConfig: [ + { + op: "CipherSaber2 Encrypt", + args: [{ "option": "Latin1", "string": "test" }, 20], + }, + ], + }, + { + name: "CipherSaber2 Decrypt", + input: "\x5d\xd9\x7f\xeb\x77\x3c\x42\x9d\xfe\x9c\x3b\x21\x63\xbd\x53\x38\x18\x7c\x36\x37", + expectedOutput: "helloworld", + recipeConfig: [ + { + op: "CipherSaber2 Decrypt", + args: [{ "option": "Latin1", "string": "test" }, 20], + }, + ], + }, + { + name: "CipherSaber2 Encrypt", + input: "", + expectedMatch: /.{10}/s, + recipeConfig: [ + { + op: "CipherSaber2 Encrypt", + args: [{ "option": "Latin1", "string": "" }, 20], + }, + ], + }, +]);