mirror of
https://github.com/gchq/CyberChef.git
synced 2025-04-23 00:06:17 -04:00
Add Salsa20 and XSalsa20 operation
This commit is contained in:
parent
c4e7c41a6e
commit
9068b6c17a
7 changed files with 595 additions and 0 deletions
|
@ -95,6 +95,8 @@
|
|||
"RC4",
|
||||
"RC4 Drop",
|
||||
"ChaCha",
|
||||
"Salsa20",
|
||||
"XSalsa20",
|
||||
"Rabbit",
|
||||
"SM4 Encrypt",
|
||||
"SM4 Decrypt",
|
||||
|
|
144
src/core/lib/Salsa20.mjs
Normal file
144
src/core/lib/Salsa20.mjs
Normal file
|
@ -0,0 +1,144 @@
|
|||
/**
|
||||
* @author joostrijneveld [joost@joostrijneveld.nl]
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Utils from "../Utils.mjs";
|
||||
|
||||
/**
|
||||
* Computes the Salsa20 permute function
|
||||
*
|
||||
* @param {byteArray} x
|
||||
* @param {integer} rounds
|
||||
*/
|
||||
function salsa20Permute(x, rounds) {
|
||||
/**
|
||||
* Macro to compute a 32-bit rotate-left operation
|
||||
*
|
||||
* @param {integer} x
|
||||
* @param {integer} n
|
||||
* @returns {integer}
|
||||
*/
|
||||
function ROL32(x, n) {
|
||||
return ((x << n) & 0xFFFFFFFF) | (x >>> (32 - n));
|
||||
}
|
||||
|
||||
/**
|
||||
* Macro to compute a single Salsa20 quarterround operation
|
||||
*
|
||||
* @param {integer} x
|
||||
* @param {integer} a
|
||||
* @param {integer} b
|
||||
* @param {integer} c
|
||||
* @param {integer} d
|
||||
* @returns {integer}
|
||||
*/
|
||||
function quarterround(x, a, b, c, d) {
|
||||
x[b] ^= ROL32((x[a] + x[d]) & 0xFFFFFFFF, 7);
|
||||
x[c] ^= ROL32((x[b] + x[a]) & 0xFFFFFFFF, 9);
|
||||
x[d] ^= ROL32((x[c] + x[b]) & 0xFFFFFFFF, 13);
|
||||
x[a] ^= ROL32((x[d] + x[c]) & 0xFFFFFFFF, 18);
|
||||
}
|
||||
|
||||
for (let i = 0; i < rounds / 2; i++) {
|
||||
quarterround(x, 0, 4, 8, 12);
|
||||
quarterround(x, 5, 9, 13, 1);
|
||||
quarterround(x, 10, 14, 2, 6);
|
||||
quarterround(x, 15, 3, 7, 11);
|
||||
quarterround(x, 0, 1, 2, 3);
|
||||
quarterround(x, 5, 6, 7, 4);
|
||||
quarterround(x, 10, 11, 8, 9);
|
||||
quarterround(x, 15, 12, 13, 14);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the Salsa20 block function
|
||||
*
|
||||
* @param {byteArray} key
|
||||
* @param {byteArray} nonce
|
||||
* @param {byteArray} counter
|
||||
* @param {integer} rounds
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
export function salsa20Block(key, nonce, counter, rounds) {
|
||||
const tau = "expand 16-byte k";
|
||||
const sigma = "expand 32-byte k";
|
||||
let state, c;
|
||||
if (key.length === 16) {
|
||||
c = Utils.strToByteArray(tau);
|
||||
key = key.concat(key);
|
||||
} else {
|
||||
c = Utils.strToByteArray(sigma);
|
||||
}
|
||||
|
||||
state = c.slice(0, 4);
|
||||
state = state.concat(key.slice(0, 16));
|
||||
state = state.concat(c.slice(4, 8));
|
||||
state = state.concat(nonce);
|
||||
state = state.concat(counter);
|
||||
state = state.concat(c.slice(8, 12));
|
||||
state = state.concat(key.slice(16, 32));
|
||||
state = state.concat(c.slice(12, 16));
|
||||
|
||||
const x = Array();
|
||||
for (let i = 0; i < 64; i += 4) {
|
||||
x.push(Utils.byteArrayToInt(state.slice(i, i + 4), "little"));
|
||||
}
|
||||
const a = [...x];
|
||||
|
||||
salsa20Permute(x, rounds);
|
||||
|
||||
for (let i = 0; i < 16; i++) {
|
||||
x[i] = (x[i] + a[i]) & 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
let output = Array();
|
||||
for (let i = 0; i < 16; i++) {
|
||||
output = output.concat(Utils.intToByteArray(x[i], 4, "little"));
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the hSalsa20 function
|
||||
*
|
||||
* @param {byteArray} key
|
||||
* @param {byteArray} nonce
|
||||
* @param {integer} rounds
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
export function hsalsa20(key, nonce, rounds) {
|
||||
const tau = "expand 16-byte k";
|
||||
const sigma = "expand 32-byte k";
|
||||
let state, c;
|
||||
if (key.length === 16) {
|
||||
c = Utils.strToByteArray(tau);
|
||||
key = key.concat(key);
|
||||
} else {
|
||||
c = Utils.strToByteArray(sigma);
|
||||
}
|
||||
|
||||
state = c.slice(0, 4);
|
||||
state = state.concat(key.slice(0, 16));
|
||||
state = state.concat(c.slice(4, 8));
|
||||
state = state.concat(nonce);
|
||||
state = state.concat(c.slice(8, 12));
|
||||
state = state.concat(key.slice(16, 32));
|
||||
state = state.concat(c.slice(12, 16));
|
||||
|
||||
const x = Array();
|
||||
for (let i = 0; i < 64; i += 4) {
|
||||
x.push(Utils.byteArrayToInt(state.slice(i, i + 4), "little"));
|
||||
}
|
||||
|
||||
salsa20Permute(x, rounds);
|
||||
|
||||
let output = Array();
|
||||
const idx = [0, 5, 10, 15, 6, 7, 8, 9];
|
||||
for (let i = 0; i < 8; i++) {
|
||||
output = output.concat(Utils.intToByteArray(x[idx[i]], 4, "little"));
|
||||
}
|
||||
return output;
|
||||
}
|
154
src/core/operations/Salsa20.mjs
Normal file
154
src/core/operations/Salsa20.mjs
Normal file
|
@ -0,0 +1,154 @@
|
|||
/**
|
||||
* @author joostrijneveld [joost@joostrijneveld.nl]
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import { toHex } from "../lib/Hex.mjs";
|
||||
import { salsa20Block } from "../lib/Salsa20.mjs";
|
||||
|
||||
/**
|
||||
* Salsa20 operation
|
||||
*/
|
||||
class Salsa20 extends Operation {
|
||||
|
||||
/**
|
||||
* Salsa20 constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Salsa20";
|
||||
this.module = "Default";
|
||||
this.description = "Salsa20 is a stream cipher designed by Daniel J. Bernstein and submitted to the eSTREAM project; Salsa20/8 and Salsa20/12 are round-reduced variants. It is closely related to the ChaCha stream cipher.<br><br><b>Key:</b> Salsa20 uses a key of 16 or 32 bytes (128 or 256 bits).<br><br><b>Nonce:</b> Salsa20 uses a nonce of 8 bytes (64 bits).<br><br><b>Counter:</b> Salsa uses a counter of 8 bytes (64 bits). The counter starts at zero at the start of the keystream, and is incremented at every 64 bytes.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Salsa20";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Key",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
"name": "Nonce",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64", "Integer"]
|
||||
},
|
||||
{
|
||||
"name": "Counter",
|
||||
"type": "number",
|
||||
"value": 0,
|
||||
"min": 0
|
||||
},
|
||||
{
|
||||
"name": "Rounds",
|
||||
"type": "option",
|
||||
"value": ["20", "12", "8"]
|
||||
},
|
||||
{
|
||||
"name": "Input",
|
||||
"type": "option",
|
||||
"value": ["Hex", "Raw"]
|
||||
},
|
||||
{
|
||||
"name": "Output",
|
||||
"type": "option",
|
||||
"value": ["Raw", "Hex"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const key = Utils.convertToByteArray(args[0].string, args[0].option),
|
||||
nonceType = args[1].option,
|
||||
rounds = parseInt(args[3], 10),
|
||||
inputType = args[4],
|
||||
outputType = args[5];
|
||||
|
||||
if (key.length !== 16 && key.length !== 32) {
|
||||
throw new OperationError(`Invalid key length: ${key.length} bytes.
|
||||
|
||||
Salsa20 uses a key of 16 or 32 bytes (128 or 256 bits).`);
|
||||
}
|
||||
|
||||
let counter, nonce;
|
||||
if (nonceType === "Integer") {
|
||||
nonce = Utils.intToByteArray(parseInt(args[1].string, 10), 8, "little");
|
||||
} else {
|
||||
nonce = Utils.convertToByteArray(args[1].string, args[1].option);
|
||||
if (!(nonce.length === 8)) {
|
||||
throw new OperationError(`Invalid nonce length: ${nonce.length} bytes.
|
||||
|
||||
Salsa20 uses a nonce of 8 bytes (64 bits).`);
|
||||
}
|
||||
}
|
||||
counter = Utils.intToByteArray(args[2], 8, "little");
|
||||
|
||||
const output = [];
|
||||
input = Utils.convertToByteArray(input, inputType);
|
||||
|
||||
let counterAsInt = Utils.byteArrayToInt(counter, "little");
|
||||
for (let i = 0; i < input.length; i += 64) {
|
||||
counter = Utils.intToByteArray(counterAsInt, 8, "little");
|
||||
const stream = salsa20Block(key, nonce, counter, rounds);
|
||||
for (let j = 0; j < 64 && i + j < input.length; j++) {
|
||||
output.push(input[i + j] ^ stream[j]);
|
||||
}
|
||||
counterAsInt++;
|
||||
}
|
||||
|
||||
if (outputType === "Hex") {
|
||||
return toHex(output);
|
||||
} else {
|
||||
return Utils.arrayBufferToStr(Uint8Array.from(output).buffer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight Salsa20
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlight(pos, args) {
|
||||
const inputType = args[4],
|
||||
outputType = args[5];
|
||||
if (inputType === "Raw" && outputType === "Raw") {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight Salsa20 in reverse
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlightReverse(pos, args) {
|
||||
const inputType = args[4],
|
||||
outputType = args[5];
|
||||
if (inputType === "Raw" && outputType === "Raw") {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Salsa20;
|
156
src/core/operations/XSalsa20.mjs
Normal file
156
src/core/operations/XSalsa20.mjs
Normal file
|
@ -0,0 +1,156 @@
|
|||
/**
|
||||
* @author joostrijneveld [joost@joostrijneveld.nl]
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import { toHex } from "../lib/Hex.mjs";
|
||||
import { salsa20Block, hsalsa20 } from "../lib/Salsa20.mjs";
|
||||
|
||||
/**
|
||||
* XSalsa20 operation
|
||||
*/
|
||||
class XSalsa20 extends Operation {
|
||||
|
||||
/**
|
||||
* XSalsa20 constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "XSalsa20";
|
||||
this.module = "Default";
|
||||
this.description = "XSalsa20 is a variant of the Salsa20 stream cipher designed by Daniel J. Bernstein; XSalsa uses longer nonces.<br><br><b>Key:</b> XSalsa20 uses a key of 16 or 32 bytes (128 or 256 bits).<br><br><b>Nonce:</b> XSalsa20 uses a nonce of 24 bytes (192 bits).<br><br><b>Counter:</b> XSalsa uses a counter of 8 bytes (64 bits). The counter starts at zero at the start of the keystream, and is incremented at every 64 bytes.";
|
||||
this.infoURL = "https://en.wikipedia.org/wiki/Salsa20#XSalsa20_with_192-bit_nonce";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Key",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
"name": "Nonce",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64", "Integer"]
|
||||
},
|
||||
{
|
||||
"name": "Counter",
|
||||
"type": "number",
|
||||
"value": 0,
|
||||
"min": 0
|
||||
},
|
||||
{
|
||||
"name": "Rounds",
|
||||
"type": "option",
|
||||
"value": ["20", "12", "8"]
|
||||
},
|
||||
{
|
||||
"name": "Input",
|
||||
"type": "option",
|
||||
"value": ["Hex", "Raw"]
|
||||
},
|
||||
{
|
||||
"name": "Output",
|
||||
"type": "option",
|
||||
"value": ["Raw", "Hex"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const key = Utils.convertToByteArray(args[0].string, args[0].option),
|
||||
nonceType = args[1].option,
|
||||
rounds = parseInt(args[3], 10),
|
||||
inputType = args[4],
|
||||
outputType = args[5];
|
||||
|
||||
if (key.length !== 16 && key.length !== 32) {
|
||||
throw new OperationError(`Invalid key length: ${key.length} bytes.
|
||||
|
||||
XSalsa20 uses a key of 16 or 32 bytes (128 or 256 bits).`);
|
||||
}
|
||||
|
||||
let counter, nonce;
|
||||
if (nonceType === "Integer") {
|
||||
nonce = Utils.intToByteArray(parseInt(args[1].string, 10), 8, "little");
|
||||
} else {
|
||||
nonce = Utils.convertToByteArray(args[1].string, args[1].option);
|
||||
if (!(nonce.length === 24)) {
|
||||
throw new OperationError(`Invalid nonce length: ${nonce.length} bytes.
|
||||
|
||||
XSalsa20 uses a nonce of 24 bytes (192 bits).`);
|
||||
}
|
||||
}
|
||||
counter = Utils.intToByteArray(args[2], 8, "little");
|
||||
|
||||
const xsalsaKey = hsalsa20(key, nonce.slice(0, 16), rounds);
|
||||
|
||||
const output = [];
|
||||
input = Utils.convertToByteArray(input, inputType);
|
||||
|
||||
let counterAsInt = Utils.byteArrayToInt(counter, "little");
|
||||
for (let i = 0; i < input.length; i += 64) {
|
||||
counter = Utils.intToByteArray(counterAsInt, 8, "little");
|
||||
const stream = salsa20Block(xsalsaKey, nonce.slice(16, 24), counter, rounds);
|
||||
for (let j = 0; j < 64 && i + j < input.length; j++) {
|
||||
output.push(input[i + j] ^ stream[j]);
|
||||
}
|
||||
counterAsInt++;
|
||||
}
|
||||
|
||||
if (outputType === "Hex") {
|
||||
return toHex(output);
|
||||
} else {
|
||||
return Utils.arrayBufferToStr(Uint8Array.from(output).buffer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight XSalsa20
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlight(pos, args) {
|
||||
const inputType = args[4],
|
||||
outputType = args[5];
|
||||
if (inputType === "Raw" && outputType === "Raw") {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight XSalsa20 in reverse
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlightReverse(pos, args) {
|
||||
const inputType = args[4],
|
||||
outputType = args[5];
|
||||
if (inputType === "Raw" && outputType === "Raw") {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default XSalsa20;
|
Loading…
Add table
Add a link
Reference in a new issue