From d0954fc3baf0e6064f8740b0344de6d47d48424d Mon Sep 17 00:00:00 2001 From: n1073645 Date: Wed, 29 Jan 2020 16:05:14 +0000 Subject: [PATCH] Hill Cipher Encode/Decode added. --- src/core/config/Categories.json | 2 + src/core/lib/HillCipher.mjs | 229 +++++++++++++++++++++++ src/core/operations/HillCipherDecode.mjs | 55 ++++++ src/core/operations/HillCipherEncode.mjs | 59 ++++++ 4 files changed, 345 insertions(+) create mode 100644 src/core/lib/HillCipher.mjs create mode 100644 src/core/operations/HillCipherDecode.mjs create mode 100644 src/core/operations/HillCipherEncode.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 53ca796d..024dac14 100755 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -95,6 +95,8 @@ "Affine Cipher Decode", "A1Z26 Cipher Encode", "A1Z26 Cipher Decode", + "Hill Cipher Encode", + "Hill Cipher Decode", "Atbash Cipher", "Substitute", "Derive PBKDF2 key", diff --git a/src/core/lib/HillCipher.mjs b/src/core/lib/HillCipher.mjs new file mode 100644 index 00000000..65564d28 --- /dev/null +++ b/src/core/lib/HillCipher.mjs @@ -0,0 +1,229 @@ +/** + * @author n1073645 [n1073645@gmail.com] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import OperationError from "../errors/OperationError.mjs"; +let N = 0; + +/** + * Generates a matrix from a string. Depending on the strings purpose it populates it differently. + * + * @param {string} theString + * @param {boolean} keyflag + * @returns {object} + */ +function genMatix(theString, keyflag=false) { + const matrix = new Array(N).fill(0).map(() => new Array(N).fill(-1)); + let count = 0; + + // Loop over string and put it into a matrix. + for (let i = 0; i < theString.length; i++) { + if (i % N === 0 && i) + count++; + if (keyflag) + matrix[count][i%N] = theString.charCodeAt(i) - 97; + else + matrix[i%N][count] = theString.charCodeAt(i) - 97; + } + return matrix; +} + +/** + * Gets the cofactor matrix of a matrix. + * + * @param {object} matrix + * @param {number} p + * @param {number} q + * @param {number} size + * @returns {object} + */ +function getCofactor(matrix, p, q, size) { + const temp = new Array(size).fill(0).map(() => new Array(size).fill(-1)); + let i = 0, j = 0; + + // Loop through all rows and columns, copying into the cofactor matrix. + for (let row = 0; row < size; row++) + for (let col = 0; col < size; col++) + if (row !== p && col !== q) { + temp[i][j++] = matrix[row][col]; + + // Reset loop counters. + if (j === size - 1) { + j = 0; + i++; + } + } + return temp; +} + +/** + * Calculates the determinant from a matrix. + * + * @param {object} matrix + * @param {number} size + * @returns {number} + */ +function determinant (matrix, size) { + let D = 0; + if (size === 1) + return matrix[0][0]; + let sign = 1; + + // Loop through top row of matrix calculating the determinant with an alternating sign. + for (let f = 0; f < size; f++) { + const temp = getCofactor(matrix, 0, f, size); + D += sign * matrix[0][f] * determinant(temp, size-1); + sign *= -1; + } + return D; +} + +/** + * Calculates the adjoint matrix from a matrix. + * + * @param {object} matrix + * @returns {object} + */ +function adjoint(matrix) { + if (N === 1) + return [[1]]; + let sign = 1; + const adj = new Array(N); + + // Calculates the adjugate matrix which is the transpose of the cofactor. + for (let i = 0; i < N; i++) { + adj[i] = new Array(N); + for (let j = 0; j< N; j++) { + const temp = getCofactor(matrix, i, j, N); + sign = ((i + j) % 2 === 0) ? 1 : -1; + adj[i][j] = sign * (determinant(temp, N - 1)); + } + } + return adj; +} + +/** + * Calculates the modular multiplicative inverse of the determinant. + * + * @param {number} det + * @param {number} base + * @returns {number} + */ +function inverseDeterminant(det, base=26) { + + // This brute forces all the possible numbers that may result in a zero remainder. + for (let i = 0; i < 26; i++) { + if ((base * i + 1) % det === 0) + return Math.floor((base * i + 1) / det); + } + return null; +} + +/** + * Calculates an inverse matrix from a matrix. + * + * @param {object} matrix + * @returns {object} + */ +function inverse(matrix) { + let det = determinant(matrix, N); + det = det - (Math.floor(det/26) * 26); + + // Calculates the modular multiplicative inverse of the determinant. + det = inverseDeterminant(det); + if (det === 0) + throw new OperationError("Key matrix has a determinant of 0."); + const adj = adjoint(matrix); + const inverse = new Array(N); + + // Multiply all values in the matrix by the new determinant. + for (let i = 0; i < N; i++) { + inverse[i] = new Array(N); + for (let j = 0; j < N; j++) { + const temp = (det * adj[j][i]); + inverse[i][j] = temp - (Math.floor(temp/26) * 26); + } + } + return inverse; +} + +/** + * Multiplies two matrices together. + * + * @param {object} matrix1 + * @param {object} matrix2 + * @returns {object} + */ +function multiply(matrix1, matrix2) { + const result = new Array(matrix2.length).fill(0).map(() => new Array(matrix2[0].length).fill(0)); + + // Loop through the columns and rows of the matrices multiplying them together. + for (let i = 0; i < matrix1.length; i++) + for (let j = 0; j < matrix2[0].length; j++) { + for (let k = 0; k < matrix2.length; k++) + result[i][j] += matrix1[i][k] * matrix2[k][j]; + result[i][j] = String.fromCharCode(97 + (result[i][j] % 26)); + } + return result; +} + +/** + * Converts a matrix into a string. + * + * @param {object} matrix + * @returns {string} + */ +function join(matrix) { + let result = ""; + + // Join values of a matrix together into a string. + for (let i = 0; i < matrix[0].length; i++) + for (let j = 0; j < matrix.length; j++) + result += matrix[j][i]; + return result; +} + +/** + * Encodes the plaintext using a key. + * + * @param {string} plaintext + * @param {string} key + * @returns {string} + */ +export function encode(plaintext, key) { + + // Don't trust users, calculate the size of the key matrix manually. + N = Math.ceil(Math.sqrt(key.length)); + + // Generate matrix representation of the key. + const keyMatrix = genMatix(key, true); + + // Generate matrix representation of the plaintext. + const plaintextMatrix = genMatix(plaintext); + + return join(multiply(keyMatrix, plaintextMatrix)); +} + +/** + * Decodes the ciphertext using a key. + * + * @param {string} ciphertext + * @param {string} key + * @returns {string} + */ +export function decode(ciphertext, key) { + + // Don't trust users, calculate the size of the key matrix manually. + N = Math.ceil(Math.sqrt(key.length)); + + // Generate matrix representation of the key. + const keyMatrix = inverse(genMatix(key, true)); + + // Generate matrix representation of the plaintext. + const ciphertextMatrix = genMatix(ciphertext); + + return join(multiply(keyMatrix, ciphertextMatrix)); + +} diff --git a/src/core/operations/HillCipherDecode.mjs b/src/core/operations/HillCipherDecode.mjs new file mode 100644 index 00000000..97a38c13 --- /dev/null +++ b/src/core/operations/HillCipherDecode.mjs @@ -0,0 +1,55 @@ +/** + * @author n1073645 [n1073645@gmail.com] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import * as HillCipher from "../lib/HillCipher.mjs"; + +/** + * Hill Cipher Decode operation + */ +class HillCipherDecode extends Operation { + + /** + * HillCipherDecode constructor + */ + constructor() { + super(); + + this.name = "Hill Cipher Decode"; + this.module = "Crypto"; + this.description = ""; + this.infoURL = ""; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Key", + type: "string", + value: "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = args[0].toLowerCase(); + input = input.toLowerCase(); + if (input.length === 0 || key.length === 0) + return ""; + + while (input.indexOf(" ") !== -1) + input = input.replace(" ", ""); + + return HillCipher.decode(input, key); + } + +} + +export default HillCipherDecode; diff --git a/src/core/operations/HillCipherEncode.mjs b/src/core/operations/HillCipherEncode.mjs new file mode 100644 index 00000000..87fcb72e --- /dev/null +++ b/src/core/operations/HillCipherEncode.mjs @@ -0,0 +1,59 @@ +/** + * @author n1073645 [n1073645@gmail.com] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import * as HillCipher from "../lib/HillCipher.mjs"; + +/** + * Hill Cipher Encode operation + */ +class HillCipherEncode extends Operation { + + /** + * HillCipherEncode constructor + */ + constructor() { + super(); + + this.name = "Hill Cipher Encode"; + this.module = "Crypto"; + this.description = ""; + this.infoURL = ""; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Key", + type: "string", + value: "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + + const key = args[0].toLowerCase(); + input = input.toLowerCase(); + + // The algorithm has to have a non-empty input and a non-empty key. + if (input.length === 0 || key.length === 0) + return ""; + + // Remove spaces from input. + while (input.indexOf(" ") !== -1) + input = input.replace(" ", ""); + + return HillCipher.encode(input, key); + } + +} + +export default HillCipherEncode;