-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'LS47Cipher' of https://github.com/n1073645/CyberChef
- Loading branch information
Showing
6 changed files
with
413 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,244 @@ | ||
/** | ||
* @author n1073645 [n1073645@gmail.com] | ||
* @copyright Crown Copyright 2020 | ||
* @license Apache-2.0 | ||
*/ | ||
|
||
import OperationError from "../errors/OperationError.mjs"; | ||
|
||
const letters = "_abcdefghijklmnopqrstuvwxyz.0123456789,-+*/:?!'()"; | ||
const tiles = []; | ||
|
||
/** | ||
* Initialises the tiles with values and positions. | ||
*/ | ||
export function initTiles() { | ||
for (let i = 0; i < 49; i++) | ||
tiles.push([letters.charAt(i), [Math.floor(i/7), i % 7]]); | ||
} | ||
|
||
/** | ||
* Rotates the key "down". | ||
* | ||
* @param {string} key | ||
* @param {number} col | ||
* @param {number} n | ||
* @returns {string} | ||
*/ | ||
function rotateDown(key, col, n) { | ||
const lines = []; | ||
for (let i = 0; i < 7; i++) | ||
lines.push(key.slice(i*7, (i + 1) * 7)); | ||
const lefts = []; | ||
let mids = []; | ||
const 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; | ||
} | ||
|
||
/** | ||
* Rotates the key "right". | ||
* | ||
* @param {string} key | ||
* @param {number} row | ||
* @param {number} n | ||
* @returns {string} | ||
*/ | ||
function rotateRight(key, row, n) { | ||
const 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)); | ||
} | ||
|
||
/** | ||
* Finds the position of a letter in the tiles. | ||
* | ||
* @param {string} letter | ||
* @returns {string} | ||
*/ | ||
function findIx(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"); | ||
} | ||
|
||
/** | ||
* Derives key from the input password. | ||
* | ||
* @param {string} password | ||
* @returns {string} | ||
*/ | ||
export function deriveKey(password) { | ||
let i = 0; | ||
let k = letters; | ||
for (const c of password) { | ||
const [row, col] = findIx(c); | ||
k = rotateDown(rotateRight(k, i, col), i, row); | ||
i = (i + 1) % 7; | ||
} | ||
return k; | ||
} | ||
|
||
/** | ||
* Checks the key is a valid key. | ||
* | ||
* @param {string} key | ||
*/ | ||
function checkKey(key) { | ||
if (key.length !== letters.length) | ||
throw new OperationError("Wrong key size"); | ||
const 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!"); | ||
} | ||
} | ||
|
||
/** | ||
* Finds the position of a letter in they key. | ||
* | ||
* @param {letter} key | ||
* @param {string} letter | ||
* @returns {object} | ||
*/ | ||
function findPos (key, letter) { | ||
const 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!"); | ||
} | ||
|
||
/** | ||
* Returns the character at the position on the tiles. | ||
* | ||
* @param {string} key | ||
* @param {object} coord | ||
* @returns {string} | ||
*/ | ||
function findAtPos(key, coord) { | ||
return key.charAt(coord[1] + (coord[0] * 7)); | ||
} | ||
|
||
/** | ||
* Returns new position by adding two positions. | ||
* | ||
* @param {object} a | ||
* @param {object} b | ||
* @returns {object} | ||
*/ | ||
function addPos(a, b) { | ||
return [(a[0] + b[0]) % 7, (a[1] + b[1]) % 7]; | ||
} | ||
|
||
/** | ||
* Returns new position by subtracting two positions. | ||
* Note: We have to manually do the remainder division, since JS does not | ||
* operate correctly on negative numbers (e.g. -3 % 4 = -3 when it should be 1). | ||
* | ||
* @param {object} a | ||
* @param {object} b | ||
* @returns {object} | ||
*/ | ||
function subPos(a, b) { | ||
const asub = a[0] - b[0]; | ||
const bsub = a[1] - b[1]; | ||
return [asub - (Math.floor(asub/7) * 7), bsub - (Math.floor(bsub/7) * 7)]; | ||
} | ||
|
||
/** | ||
* Encrypts the plaintext string. | ||
* | ||
* @param {string} key | ||
* @param {string} plaintext | ||
* @returns {string} | ||
*/ | ||
function encrypt(key, plaintext) { | ||
checkKey(key); | ||
let mp = [0, 0]; | ||
let ciphertext = ""; | ||
for (const p of plaintext) { | ||
const pp = findPos(key, p); | ||
const mix = findIx(findAtPos(key, mp)); | ||
let cp = addPos(pp, mix); | ||
const c = findAtPos(key, cp); | ||
ciphertext += c; | ||
key = rotateRight(key, pp[0], 1); | ||
cp = findPos(key, c); | ||
key = rotateDown(key, cp[1], 1); | ||
mp = addPos(mp, findIx(c)); | ||
} | ||
return ciphertext; | ||
} | ||
|
||
/** | ||
* Decrypts the ciphertext string. | ||
* | ||
* @param {string} key | ||
* @param {string} ciphertext | ||
* @returns {string} | ||
*/ | ||
function decrypt(key, ciphertext) { | ||
checkKey(key); | ||
let mp = [0, 0]; | ||
let plaintext = ""; | ||
for (const c of ciphertext) { | ||
let cp = findPos(key, c); | ||
const mix = findIx(findAtPos(key, mp)); | ||
const pp = subPos(cp, mix); | ||
const p = findAtPos(key, pp); | ||
plaintext += p; | ||
key = rotateRight(key, pp[0], 1); | ||
cp = findPos(key, c); | ||
key = rotateDown(key, cp[1], 1); | ||
mp = addPos(mp, findIx(c)); | ||
} | ||
return plaintext; | ||
} | ||
|
||
/** | ||
* Adds padding to the input. | ||
* | ||
* @param {string} key | ||
* @param {string} plaintext | ||
* @param {string} signature | ||
* @param {number} paddingSize | ||
* @returns {string} | ||
*/ | ||
export function encryptPad(key, plaintext, signature, paddingSize) { | ||
initTiles(); | ||
checkKey(key); | ||
let padding = ""; | ||
for (let i = 0; i < paddingSize; i++) { | ||
padding += letters.charAt(Math.floor(Math.random() * letters.length)); | ||
} | ||
return encrypt(key, padding+plaintext+"---"+signature); | ||
} | ||
|
||
/** | ||
* Removes padding from the ouput. | ||
* | ||
* @param {string} key | ||
* @param {string} ciphertext | ||
* @param {number} paddingSize | ||
* @returns {string} | ||
*/ | ||
export function decryptPad(key, ciphertext, paddingSize) { | ||
initTiles(); | ||
checkKey(key); | ||
return decrypt(key, ciphertext).slice(paddingSize); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
/** | ||
* @author n1073645 [n1073645@gmail.com] | ||
* @copyright Crown Copyright 2020 | ||
* @license Apache-2.0 | ||
*/ | ||
|
||
import Operation from "../Operation.mjs"; | ||
import * as LS47 from "../lib/LS47.mjs"; | ||
|
||
/** | ||
* LS47 Decrypt operation | ||
*/ | ||
class LS47Decrypt extends Operation { | ||
|
||
/** | ||
* LS47Decrypt constructor | ||
*/ | ||
constructor() { | ||
super(); | ||
|
||
this.name = "LS47 Decrypt"; | ||
this.module = "Crypto"; | ||
this.description = "This is a slight improvement of the ElsieFour cipher as described by Alan Kaminsky. We use 7x7 characters instead of original (barely fitting) 6x6, to be able to encrypt some structured information. We also describe a simple key-expansion algorithm, because remembering passwords is popular. Similar security considerations as with ElsieFour hold.\nThe LS47 alphabet consists of following characters: _abcdefghijklmnopqrstuvwxyz.0123456789,-+*/:?!'()\nA LS47 key is a permutation of the alphabet that is then represented in a 7x7 grid used for the encryption or decryption."; | ||
this.infoURL = "https://gitea.blesmrt.net/exa/ls47/src/branch/master"; | ||
this.inputType = "string"; | ||
this.outputType = "string"; | ||
this.args = [ | ||
{ | ||
name: "Password", | ||
type: "string", | ||
value: "" | ||
}, | ||
{ | ||
name: "Padding", | ||
type: "number", | ||
value: 10 | ||
} | ||
]; | ||
} | ||
|
||
/** | ||
* @param {string} input | ||
* @param {Object[]} args | ||
* @returns {string} | ||
*/ | ||
run(input, args) { | ||
|
||
this.paddingSize = parseInt(args[1], 10); | ||
|
||
LS47.initTiles(); | ||
|
||
const key = LS47.deriveKey(args[0]); | ||
return LS47.decryptPad(key, input, this.paddingSize); | ||
} | ||
|
||
} | ||
|
||
export default LS47Decrypt; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
/** | ||
* @author n1073645 [n1073645@gmail.com] | ||
* @copyright Crown Copyright 2020 | ||
* @license Apache-2.0 | ||
*/ | ||
|
||
import Operation from "../Operation.mjs"; | ||
import * as LS47 from "../lib/LS47.mjs"; | ||
|
||
/** | ||
* LS47 Encrypt operation | ||
*/ | ||
class LS47Encrypt extends Operation { | ||
|
||
/** | ||
* LS47Encrypt constructor | ||
*/ | ||
constructor() { | ||
super(); | ||
|
||
this.name = "LS47 Encrypt"; | ||
this.module = "Crypto"; | ||
this.description = "This is a slight improvement of the ElsieFour cipher as described by Alan Kaminsky. We use 7x7 characters instead of original (barely fitting) 6x6, to be able to encrypt some structured information. We also describe a simple key-expansion algorithm, because remembering passwords is popular. Similar security considerations as with ElsieFour hold.\nThe LS47 alphabet consists of following characters: _abcdefghijklmnopqrstuvwxyz.0123456789,-+*/:?!'()\nA LS47 key is a permutation of the alphabet that is then represented in a 7x7 grid used for the encryption or decryption."; | ||
this.infoURL = "https://gitea.blesmrt.net/exa/ls47/src/branch/master"; | ||
this.inputType = "string"; | ||
this.outputType = "string"; | ||
this.args = [ | ||
{ | ||
name: "Password", | ||
type: "string", | ||
value: "" | ||
}, | ||
{ | ||
name: "Padding", | ||
type: "number", | ||
value: 10 | ||
}, | ||
{ | ||
name: "Signature", | ||
type: "string", | ||
value: "" | ||
} | ||
]; | ||
} | ||
|
||
/** | ||
* @param {string} input | ||
* @param {Object[]} args | ||
* @returns {string} | ||
*/ | ||
run(input, args) { | ||
|
||
this.paddingSize = parseInt(args[1], 10); | ||
|
||
LS47.initTiles(); | ||
|
||
const key = LS47.deriveKey(args[0]); | ||
return LS47.encryptPad(key, input, args[2], this.paddingSize); | ||
} | ||
|
||
} | ||
|
||
export default LS47Encrypt; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.