Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Salsa20 and XSalsa20 operation #1750

Merged
merged 1 commit into from
Mar 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading