Skip to content

Commit

Permalink
Add ChaCha stream cipher operation
Browse files Browse the repository at this point in the history
  • Loading branch information
joostrijneveld committed Nov 3, 2022
1 parent ed8bd34 commit 3b887b8
Show file tree
Hide file tree
Showing 5 changed files with 428 additions and 0 deletions.
63 changes: 63 additions & 0 deletions src/core/Utils.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,69 @@ class Utils {
}
}

/**
* Converts a byte array to an integer.
*
* @param {byteArray} byteArray
* @param {string} byteorder - 'little' or 'big'
* @returns {integer}
*
* @example
* // returns 67305985
* Utils.byteArrayToInt([ 1, 2, 3, 4], 'little');
*
* // returns 16909060
* Utils.byteArrayToInt([ 1, 2, 3, 4], 'big');
*/
static byteArrayToInt(byteArray, byteorder) {
var value = 0;
if (byteorder == 'big') {
for (var i = 0; i < byteArray.length; i++) {
value = (value * 256) + byteArray[i];
}
}
else {
for (var i = byteArray.length - 1; i >= 0; i--) {
value = (value * 256) + byteArray[i];
}
}
return value;
};

/**
* Converts an integer to a byte array of {length} bytes.
*
* @param {integer} value
* @param {integer} length
* @param {string} byteorder - 'little' or 'big'
* @returns {byteArray}
*
* @example
* // returns [ 5, 255, 109, 1 ]
* Utils.intToByteArray(23985925, 4, 'little');
*
* // returns [ 1, 109, 255, 5 ]
* Utils.intToByteArray(23985925, 4, 'big');
*
* // returns [ 0, 0, 0, 0, 1, 109, 255, 5 ]
* Utils.intToByteArray(23985925, 8, 'big');
*/
static intToByteArray(value, length, byteorder) {
const arr = new Array(length);
if (byteorder == 'little') {
for (var i = 0; i < length; i++) {
arr[i] = value & 0xFF;
value = value >>> 8;
}
}
else {
for (var i = length - 1; i >= 0; i--) {
arr[i] = value & 0xFF;
value = value >>> 8;
}
}
return arr;
};

/**
* Converts a string to an ArrayBuffer.
Expand Down
1 change: 1 addition & 0 deletions src/core/config/Categories.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"AES Decrypt",
"Blowfish Encrypt",
"Blowfish Decrypt",
"ChaCha",
"DES Encrypt",
"DES Decrypt",
"Triple DES Encrypt",
Expand Down
212 changes: 212 additions & 0 deletions src/core/operations/ChaCha.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
/**
* @author joostrijneveld [joost@joostrijneveld.nl]
* @copyright Crown Copyright 2022
* @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";

function chacha(key, nonce, counter, rounds) {
const tau = "expand 16-byte k";
const sigma = "expand 32-byte k";

var state, c;
if (key.length == 16) {
c = Utils.strToByteArray(tau);
state = c.concat(key).concat(key);
}
else {
c = Utils.strToByteArray(sigma);
state = c.concat(key);
}
state = state.concat(counter).concat(nonce);

var x = Array();
for (var i = 0; i < 64; i += 4) {
x.push(Utils.byteArrayToInt(state.slice(i, i + 4), 'little'))
}
var a = [...x];

function ROL32(x, n) {
return ((x << n) & 0xFFFFFFFF) | (x >>> (32 - n));
}

function quarterround(x, a, b, c, d) {
x[a] = ((x[a] + x[b]) & 0xFFFFFFFF); x[d] = ROL32(x[d] ^ x[a], 16);
x[c] = ((x[c] + x[d]) & 0xFFFFFFFF); x[b] = ROL32(x[b] ^ x[c], 12);
x[a] = ((x[a] + x[b]) & 0xFFFFFFFF); x[d] = ROL32(x[d] ^ x[a], 8);
x[c] = ((x[c] + x[d]) & 0xFFFFFFFF); x[b] = ROL32(x[b] ^ x[c], 7);
}

for (var i = 0; i < rounds / 2; i++) {
quarterround(x, 0, 4, 8, 12)
quarterround(x, 1, 5, 9, 13)
quarterround(x, 2, 6, 10, 14)
quarterround(x, 3, 7, 11, 15)
quarterround(x, 0, 5, 10, 15)
quarterround(x, 1, 6, 11, 12)
quarterround(x, 2, 7, 8, 13)
quarterround(x, 3, 4, 9, 14)
}

for (var i = 0; i < 16; i++) {
x[i] = (x[i] + a[i]) & 0xFFFFFFFF
}

var output = Array();
for (var i = 0; i < 16; i++) {
output = output.concat(Utils.intToByteArray(x[i], 4, 'little'));
}
return output;
}

/**
* ChaCha operation
*/
class ChaCha extends Operation {

/**
* ChaCha constructor
*/
constructor() {
super();

this.name = "ChaCha";
this.module = "Default";
this.description = "ChaCha is a stream cipher designed by Daniel J. Bernstein. It is a variant of the Salsa stream cipher. Several parameterizations exist; 'ChaCha' may refer to the original construction, or to the variant as described in RFC-8439. ChaCha is often used with Poly1305, in the ChaCha20-Poly1305 AEAD construction.<br><br><b>Key:</b> ChaCha uses a key of 16 or 32 bytes (128 or 256 bits).<br><br><b>Nonce:</b> ChaCha uses a nonce of 8 or 12 bytes (64 or 96 bits).<br><br><b>Counter:</b> ChaCha uses a counter of 4 or 8 bytes (32 or 64 bits); together, the nonce and counter must add up to 16 bytes. 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#ChaCha_variant";
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,
counterType = args[2].option,
rounds = parseInt(args[3]),
inputType = args[4],
outputType = args[5];

if (key.length !== 16 && key.length !== 32) {
throw new OperationError(`Invalid key length: ${key.length} bytes.
ChaCha uses a key of 16 or 32 bytes (128 or 256 bits).`);
}
var counter, nonce;
var counterLength;
if (nonceType !== 'Integer') {
nonce = Utils.convertToByteArray(args[1].string, args[1].option)
if (!(nonce.length == 12 || nonce.length == 8)) {
throw new OperationError(`Invalid nonce length: ${nonce.length} bytes.
ChaCha uses a nonce of 8 or 12 bytes (64 or 96 bits).`);
}
counterLength = 16 - nonce.length;
counter = Utils.intToByteArray(args[2], counterLength, 'little');
}
if (nonceType === 'Integer') {
nonce = Utils.intToByteArray(parseInt(args[1].string), 12, 'little')
counter = Utils.intToByteArray(args[2], 4, 'little');
}
var output = new Array();

input = Utils.convertToByteArray(input, inputType);

var counterAsInt = Utils.byteArrayToInt(counter, 'little');
for (var i = 0; i < input.length; i += 64) {
counter = Utils.intToByteArray(counterAsInt, counterLength, 'little');
var stream = chacha(key, nonce, counter, rounds);
for (var 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(output);
}
}

/**
* Highlight ChaCha
*
* @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 ChaCha 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 ChaCha;
1 change: 1 addition & 0 deletions tests/operations/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import "./tests/ByteRepr.mjs";
import "./tests/CartesianProduct.mjs";
import "./tests/CetaceanCipherEncode.mjs";
import "./tests/CetaceanCipherDecode.mjs";
import "./tests/ChaCha.mjs";
import "./tests/CharEnc.mjs";
import "./tests/ChangeIPFormat.mjs";
import "./tests/Charts.mjs";
Expand Down
Loading

0 comments on commit 3b887b8

Please sign in to comment.