Skip to content

Commit

Permalink
Merge pull request #1750 from joostrijneveld/feature/salsa20
Browse files Browse the repository at this point in the history
  • Loading branch information
a3957273 authored Mar 29, 2024
2 parents 877c83e + 9068b6c commit 0f14d23
Show file tree
Hide file tree
Showing 7 changed files with 595 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/core/config/Categories.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@
"RC4",
"RC4 Drop",
"ChaCha",
"Salsa20",
"XSalsa20",
"Rabbit",
"SM4 Encrypt",
"SM4 Decrypt",
Expand Down
144 changes: 144 additions & 0 deletions src/core/lib/Salsa20.mjs
Original file line number Diff line number Diff line change
@@ -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 changes: 154 additions & 0 deletions src/core/operations/Salsa20.mjs
Original file line number Diff line number Diff line change
@@ -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;
Loading

0 comments on commit 0f14d23

Please sign in to comment.