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", "Affine Cipher Decode",
"A1Z26 Cipher Encode", "A1Z26 Cipher Encode",
"A1Z26 Cipher Decode", "A1Z26 Cipher Decode",
"Hill Cipher Encode",
"Hill Cipher Decode",
"Atbash Cipher", "Atbash Cipher",
"Substitute", "Substitute",
"Derive PBKDF2 key", "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;