Skip to content

Commit 0f14d23

Browse files
authored
Merge pull request #1750 from joostrijneveld/feature/salsa20
2 parents 877c83e + 9068b6c commit 0f14d23

File tree

7 files changed

+595
-0
lines changed

7 files changed

+595
-0
lines changed

src/core/config/Categories.json

+2
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@
9595
"RC4",
9696
"RC4 Drop",
9797
"ChaCha",
98+
"Salsa20",
99+
"XSalsa20",
98100
"Rabbit",
99101
"SM4 Encrypt",
100102
"SM4 Decrypt",

src/core/lib/Salsa20.mjs

+144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/**
2+
* @author joostrijneveld [[email protected]]
3+
* @copyright Crown Copyright 2024
4+
* @license Apache-2.0
5+
*/
6+
7+
import Utils from "../Utils.mjs";
8+
9+
/**
10+
* Computes the Salsa20 permute function
11+
*
12+
* @param {byteArray} x
13+
* @param {integer} rounds
14+
*/
15+
function salsa20Permute(x, rounds) {
16+
/**
17+
* Macro to compute a 32-bit rotate-left operation
18+
*
19+
* @param {integer} x
20+
* @param {integer} n
21+
* @returns {integer}
22+
*/
23+
function ROL32(x, n) {
24+
return ((x << n) & 0xFFFFFFFF) | (x >>> (32 - n));
25+
}
26+
27+
/**
28+
* Macro to compute a single Salsa20 quarterround operation
29+
*
30+
* @param {integer} x
31+
* @param {integer} a
32+
* @param {integer} b
33+
* @param {integer} c
34+
* @param {integer} d
35+
* @returns {integer}
36+
*/
37+
function quarterround(x, a, b, c, d) {
38+
x[b] ^= ROL32((x[a] + x[d]) & 0xFFFFFFFF, 7);
39+
x[c] ^= ROL32((x[b] + x[a]) & 0xFFFFFFFF, 9);
40+
x[d] ^= ROL32((x[c] + x[b]) & 0xFFFFFFFF, 13);
41+
x[a] ^= ROL32((x[d] + x[c]) & 0xFFFFFFFF, 18);
42+
}
43+
44+
for (let i = 0; i < rounds / 2; i++) {
45+
quarterround(x, 0, 4, 8, 12);
46+
quarterround(x, 5, 9, 13, 1);
47+
quarterround(x, 10, 14, 2, 6);
48+
quarterround(x, 15, 3, 7, 11);
49+
quarterround(x, 0, 1, 2, 3);
50+
quarterround(x, 5, 6, 7, 4);
51+
quarterround(x, 10, 11, 8, 9);
52+
quarterround(x, 15, 12, 13, 14);
53+
}
54+
}
55+
56+
/**
57+
* Computes the Salsa20 block function
58+
*
59+
* @param {byteArray} key
60+
* @param {byteArray} nonce
61+
* @param {byteArray} counter
62+
* @param {integer} rounds
63+
* @returns {byteArray}
64+
*/
65+
export function salsa20Block(key, nonce, counter, rounds) {
66+
const tau = "expand 16-byte k";
67+
const sigma = "expand 32-byte k";
68+
let state, c;
69+
if (key.length === 16) {
70+
c = Utils.strToByteArray(tau);
71+
key = key.concat(key);
72+
} else {
73+
c = Utils.strToByteArray(sigma);
74+
}
75+
76+
state = c.slice(0, 4);
77+
state = state.concat(key.slice(0, 16));
78+
state = state.concat(c.slice(4, 8));
79+
state = state.concat(nonce);
80+
state = state.concat(counter);
81+
state = state.concat(c.slice(8, 12));
82+
state = state.concat(key.slice(16, 32));
83+
state = state.concat(c.slice(12, 16));
84+
85+
const x = Array();
86+
for (let i = 0; i < 64; i += 4) {
87+
x.push(Utils.byteArrayToInt(state.slice(i, i + 4), "little"));
88+
}
89+
const a = [...x];
90+
91+
salsa20Permute(x, rounds);
92+
93+
for (let i = 0; i < 16; i++) {
94+
x[i] = (x[i] + a[i]) & 0xFFFFFFFF;
95+
}
96+
97+
let output = Array();
98+
for (let i = 0; i < 16; i++) {
99+
output = output.concat(Utils.intToByteArray(x[i], 4, "little"));
100+
}
101+
return output;
102+
}
103+
104+
/**
105+
* Computes the hSalsa20 function
106+
*
107+
* @param {byteArray} key
108+
* @param {byteArray} nonce
109+
* @param {integer} rounds
110+
* @returns {byteArray}
111+
*/
112+
export function hsalsa20(key, nonce, rounds) {
113+
const tau = "expand 16-byte k";
114+
const sigma = "expand 32-byte k";
115+
let state, c;
116+
if (key.length === 16) {
117+
c = Utils.strToByteArray(tau);
118+
key = key.concat(key);
119+
} else {
120+
c = Utils.strToByteArray(sigma);
121+
}
122+
123+
state = c.slice(0, 4);
124+
state = state.concat(key.slice(0, 16));
125+
state = state.concat(c.slice(4, 8));
126+
state = state.concat(nonce);
127+
state = state.concat(c.slice(8, 12));
128+
state = state.concat(key.slice(16, 32));
129+
state = state.concat(c.slice(12, 16));
130+
131+
const x = Array();
132+
for (let i = 0; i < 64; i += 4) {
133+
x.push(Utils.byteArrayToInt(state.slice(i, i + 4), "little"));
134+
}
135+
136+
salsa20Permute(x, rounds);
137+
138+
let output = Array();
139+
const idx = [0, 5, 10, 15, 6, 7, 8, 9];
140+
for (let i = 0; i < 8; i++) {
141+
output = output.concat(Utils.intToByteArray(x[idx[i]], 4, "little"));
142+
}
143+
return output;
144+
}

src/core/operations/Salsa20.mjs

+154
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/**
2+
* @author joostrijneveld [[email protected]]
3+
* @copyright Crown Copyright 2024
4+
* @license Apache-2.0
5+
*/
6+
7+
import Operation from "../Operation.mjs";
8+
import OperationError from "../errors/OperationError.mjs";
9+
import Utils from "../Utils.mjs";
10+
import { toHex } from "../lib/Hex.mjs";
11+
import { salsa20Block } from "../lib/Salsa20.mjs";
12+
13+
/**
14+
* Salsa20 operation
15+
*/
16+
class Salsa20 extends Operation {
17+
18+
/**
19+
* Salsa20 constructor
20+
*/
21+
constructor() {
22+
super();
23+
24+
this.name = "Salsa20";
25+
this.module = "Default";
26+
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.";
27+
this.infoURL = "https://wikipedia.org/wiki/Salsa20";
28+
this.inputType = "string";
29+
this.outputType = "string";
30+
this.args = [
31+
{
32+
"name": "Key",
33+
"type": "toggleString",
34+
"value": "",
35+
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
36+
},
37+
{
38+
"name": "Nonce",
39+
"type": "toggleString",
40+
"value": "",
41+
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64", "Integer"]
42+
},
43+
{
44+
"name": "Counter",
45+
"type": "number",
46+
"value": 0,
47+
"min": 0
48+
},
49+
{
50+
"name": "Rounds",
51+
"type": "option",
52+
"value": ["20", "12", "8"]
53+
},
54+
{
55+
"name": "Input",
56+
"type": "option",
57+
"value": ["Hex", "Raw"]
58+
},
59+
{
60+
"name": "Output",
61+
"type": "option",
62+
"value": ["Raw", "Hex"]
63+
}
64+
];
65+
}
66+
67+
/**
68+
* @param {string} input
69+
* @param {Object[]} args
70+
* @returns {string}
71+
*/
72+
run(input, args) {
73+
const key = Utils.convertToByteArray(args[0].string, args[0].option),
74+
nonceType = args[1].option,
75+
rounds = parseInt(args[3], 10),
76+
inputType = args[4],
77+
outputType = args[5];
78+
79+
if (key.length !== 16 && key.length !== 32) {
80+
throw new OperationError(`Invalid key length: ${key.length} bytes.
81+
82+
Salsa20 uses a key of 16 or 32 bytes (128 or 256 bits).`);
83+
}
84+
85+
let counter, nonce;
86+
if (nonceType === "Integer") {
87+
nonce = Utils.intToByteArray(parseInt(args[1].string, 10), 8, "little");
88+
} else {
89+
nonce = Utils.convertToByteArray(args[1].string, args[1].option);
90+
if (!(nonce.length === 8)) {
91+
throw new OperationError(`Invalid nonce length: ${nonce.length} bytes.
92+
93+
Salsa20 uses a nonce of 8 bytes (64 bits).`);
94+
}
95+
}
96+
counter = Utils.intToByteArray(args[2], 8, "little");
97+
98+
const output = [];
99+
input = Utils.convertToByteArray(input, inputType);
100+
101+
let counterAsInt = Utils.byteArrayToInt(counter, "little");
102+
for (let i = 0; i < input.length; i += 64) {
103+
counter = Utils.intToByteArray(counterAsInt, 8, "little");
104+
const stream = salsa20Block(key, nonce, counter, rounds);
105+
for (let j = 0; j < 64 && i + j < input.length; j++) {
106+
output.push(input[i + j] ^ stream[j]);
107+
}
108+
counterAsInt++;
109+
}
110+
111+
if (outputType === "Hex") {
112+
return toHex(output);
113+
} else {
114+
return Utils.arrayBufferToStr(Uint8Array.from(output).buffer);
115+
}
116+
}
117+
118+
/**
119+
* Highlight Salsa20
120+
*
121+
* @param {Object[]} pos
122+
* @param {number} pos[].start
123+
* @param {number} pos[].end
124+
* @param {Object[]} args
125+
* @returns {Object[]} pos
126+
*/
127+
highlight(pos, args) {
128+
const inputType = args[4],
129+
outputType = args[5];
130+
if (inputType === "Raw" && outputType === "Raw") {
131+
return pos;
132+
}
133+
}
134+
135+
/**
136+
* Highlight Salsa20 in reverse
137+
*
138+
* @param {Object[]} pos
139+
* @param {number} pos[].start
140+
* @param {number} pos[].end
141+
* @param {Object[]} args
142+
* @returns {Object[]} pos
143+
*/
144+
highlightReverse(pos, args) {
145+
const inputType = args[4],
146+
outputType = args[5];
147+
if (inputType === "Raw" && outputType === "Raw") {
148+
return pos;
149+
}
150+
}
151+
152+
}
153+
154+
export default Salsa20;

0 commit comments

Comments
 (0)