CyberChef/src/core/lib/LS47.mjs

148 lines
4.1 KiB
JavaScript
Raw Normal View History

2020-01-27 17:07:54 +01:00
/**
* @author n1073645 [n1073645@gmail.com]
* @copyright Crown Copyright 2020
* @license Apache-2.0
*/
import OperationError from "../errors/OperationError.mjs";
let letters = "_abcdefghijklmnopqrstuvwxyz.0123456789,-+*/:?!'()";
let tiles = [];
export function init_tiles() {
for (let i = 0; i < 49; i++)
tiles.push([letters.charAt(i), [Math.floor(i/7), i % 7]]);
}
function rotate_down(key, col, n) {
let lines = [];
for (let i = 0; i < 7; i++)
lines.push(key.slice(i*7, (i + 1) * 7));
let lefts = [];
let mids = [];
let rights = [];
lines.forEach((element) => {
lefts.push(element.slice(0, col));
mids.push(element.charAt(col));
rights.push(element.slice(col+1));
});
n = (7 - n % 7) % 7;
mids = mids.slice(n).concat(mids.slice(0, n));
let result = "";
for (let i = 0; i < 7; i++)
result += lefts[i] + mids[i] + rights[i];
return result;
}
function rotate_right(key, row, n) {
let mid = key.slice(row * 7, (row + 1) * 7);
n = (7 - n % 7) % 7;
return key.slice(0, 7 * row) + mid.slice(n) + mid.slice(0, n) + key.slice(7 * (row + 1));
}
function find_ix(letter) {
for (let i = 0; i < tiles.length; i++)
if (tiles[i][0] === letter)
return tiles[i][1];
throw new OperationError("Letter " + letter + " is not included in LS47");
}
export function derive_key(password) {
let i = 0;
let k = letters;
for (const c of password) {
let [row, col] = find_ix(c);
k = rotate_down(rotate_right(k, i, col), i, row);
i = (i + 1) % 7;
}
return k;
}
function check_key(key) {
if (key.length !== letters.length)
throw new OperationError("Wrong key size");
let counts = new Array();
for (let i = 0; i < letters.length; i++)
counts[letters.charAt(i)] = 0;
for (const elem of letters){
if (letters.indexOf(elem) === -1)
throw new OperationError("Letter " + elem + " not in LS47!");
counts[elem]++;
if (counts[elem] > 1)
throw new OperationError("Letter duplicated in the key!");
}
}
function find_pos (key, letter) {
let index = key.indexOf(letter);
if (index >= 0 && index < 49)
return [Math.floor(index/7), index%7];
throw new OperationError("Letter " + letter + " is not in the key!");
}
function find_at_pos(key, coord) {
return key.charAt(coord[1] + (coord[0] * 7));
}
function add_pos(a, b) {
return [(a[0] + b[0]) % 7, (a[1] + b[1]) % 7];
}
function sub_pos(a, b) {
let asub = a[0] - b[0];
let bsub = a[1] - b[1];
return [asub - (Math.floor(asub/7) * 7), bsub - (Math.floor(bsub/7) * 7)];
}
function encrypt(key, plaintext) {
check_key(key);
let mp = [0, 0];
let ciphertext = '';
for (const p of plaintext) {
let pp = find_pos(key, p);
let mix = find_ix(find_at_pos(key, mp));
let cp = add_pos(pp, mix);
let c = find_at_pos(key, cp);
ciphertext += c;
key = rotate_right(key, pp[0], 1);
cp = find_pos(key, c);
key = rotate_down(key, cp[1], 1);
mp = add_pos(mp, find_ix(c));
}
return ciphertext;
}
function decrypt(key, ciphertext) {
check_key(key);
let mp = [0,0];
let plaintext = '';
for (const c of ciphertext) {
let cp = find_pos(key, c);
let mix = find_ix(find_at_pos(key, mp));
let pp = sub_pos(cp, mix);
let p = find_at_pos(key, pp);
plaintext += p;
key = rotate_right(key, pp[0], 1);
cp = find_pos(key, c);
key = rotate_down(key, cp[1], 1);
mp = add_pos(mp, find_ix(c));
}
return plaintext;
}
export function encrypt_pad(key, plaintext, signature, padding_size) {
init_tiles();
check_key(key);
let padding = "";
for (let i = 0; i < padding_size; i++) {
padding += letters.charAt(Math.floor(Math.random() * letters.length));
}
return encrypt(key, padding+plaintext+'---'+signature);
}
export function decrypt_pad(key, ciphertext, padding_size) {
init_tiles();
check_key(key);
return decrypt(key, ciphertext).slice(padding_size);
}