-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1750 from joostrijneveld/feature/salsa20
- Loading branch information
Showing
7 changed files
with
595 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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -95,6 +95,8 @@ | |
"RC4", | ||
"RC4 Drop", | ||
"ChaCha", | ||
"Salsa20", | ||
"XSalsa20", | ||
"Rabbit", | ||
"SM4 Encrypt", | ||
"SM4 Decrypt", | ||
|
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,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; | ||
} |
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,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; |
Oops, something went wrong.