Hill Cipher Encode/Decode added.

This commit is contained in:
n1073645 2020-01-29 16:05:14 +00:00
parent ace8121d0e
commit d0954fc3ba
4 changed files with 345 additions and 0 deletions

View file

@ -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",

229
src/core/lib/HillCipher.mjs Normal file
View file

@ -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));
}

View file

@ -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;

View file

@ -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;