mirror of
https://github.com/gchq/CyberChef.git
synced 2025-05-14 01:56:54 -04:00
Add Salsa20 operations
This commit is contained in:
parent
f7be8d720b
commit
4df8606f7b
4 changed files with 422 additions and 0 deletions
|
@ -79,6 +79,8 @@
|
||||||
"RC2 Decrypt",
|
"RC2 Decrypt",
|
||||||
"RC4",
|
"RC4",
|
||||||
"RC4 Drop",
|
"RC4 Drop",
|
||||||
|
"Salsa20 Encrypt",
|
||||||
|
"Salsa20 Decrypt",
|
||||||
"ROT13",
|
"ROT13",
|
||||||
"ROT47",
|
"ROT47",
|
||||||
"XOR",
|
"XOR",
|
||||||
|
|
241
src/core/lib/Salsa20.mjs
Normal file
241
src/core/lib/Salsa20.mjs
Normal file
|
@ -0,0 +1,241 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2017, Bubelich Mykola
|
||||||
|
* https://www.bubelich.com
|
||||||
|
*
|
||||||
|
* (。◕‿‿◕。)
|
||||||
|
*
|
||||||
|
* Modified by cbeuw (Andy Wang)
|
||||||
|
*
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* Neither the name of the copyright holder nor the names of its contributors
|
||||||
|
* may be used to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* General information
|
||||||
|
* Salsa20 is a stream cipher submitted to eSTREAM by Daniel J. Bernstein.
|
||||||
|
* It is built on a pseudorandom function based on add-rotate-xor (ARX) operations — 32-bit addition,
|
||||||
|
* bitwise addition (XOR) and rotation operations. Salsa20 maps a 256-bit key, a 64-bit nonce,
|
||||||
|
* and a 64-bit stream position to a 512-bit block of the key stream (a version with a 128-bit key also exists).
|
||||||
|
* This gives Salsa20 the unusual advantage that the user can efficiently seek to any position in the key
|
||||||
|
* stream in constant time. It offers speeds of around 4–14 cycles per byte in software on modern x86 processors,
|
||||||
|
* and reasonable hardware performance. It is not patented, and Bernstein has written several
|
||||||
|
* public domain implementations optimized for common architectures.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct SalSa20 instance with key and nonce
|
||||||
|
* Key should be Uint8Array with 32 bytes
|
||||||
|
* None should be Uint8Array with 8 bytes
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @throws {Error}
|
||||||
|
* @param {[number]} key
|
||||||
|
* @param {[number]} nonce
|
||||||
|
*/
|
||||||
|
const Salsa20 = function (key, nonce) {
|
||||||
|
if (key.length !== 16 && key.length !== 32) {
|
||||||
|
throw new Error("Key should be a 16 or 32 byte array!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nonce.length !== 8) {
|
||||||
|
throw new Error("Nonce should be 8 byte array!");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.rounds = 20;
|
||||||
|
const sigma = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574];
|
||||||
|
const tau = [0x61707865, 0x3120646e, 0x79622d36, 0x6b206574];
|
||||||
|
|
||||||
|
let constant, k;
|
||||||
|
if (key.length === 32) {
|
||||||
|
constant = sigma;
|
||||||
|
k = key;
|
||||||
|
} else {
|
||||||
|
constant = tau;
|
||||||
|
k = key.concat(k);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.param = [
|
||||||
|
// Constant
|
||||||
|
constant[0],
|
||||||
|
// Key
|
||||||
|
_get32(k, 0),
|
||||||
|
_get32(k, 4),
|
||||||
|
_get32(k, 8),
|
||||||
|
_get32(k, 12),
|
||||||
|
constant[1],
|
||||||
|
// Nonce
|
||||||
|
_get32(nonce, 0),
|
||||||
|
_get32(nonce, 4),
|
||||||
|
// Counter
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
// Constant
|
||||||
|
constant[2],
|
||||||
|
// Key
|
||||||
|
_get32(k, 16),
|
||||||
|
_get32(k, 20),
|
||||||
|
_get32(k, 24),
|
||||||
|
_get32(k, 28),
|
||||||
|
// Const
|
||||||
|
constant[3]
|
||||||
|
];
|
||||||
|
|
||||||
|
// init block 64 bytes //
|
||||||
|
this.block = [
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||||
|
];
|
||||||
|
|
||||||
|
// internal byte counter //
|
||||||
|
this.byteCounter = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypt or Decrypt data with key and nonce
|
||||||
|
*
|
||||||
|
* @param {[number]} data
|
||||||
|
* @return {Uint8Array}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
Salsa20.prototype._update = function (data) {
|
||||||
|
/*
|
||||||
|
if (data.length === 0) {
|
||||||
|
throw new Error("Data should be type of bytes (Uint8Array) and not empty!");
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
const output = new Uint8Array(data.length);
|
||||||
|
|
||||||
|
// core function, build block and xor with input data //
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
if (this.byteCounter === 0 || this.byteCounter === 64) {
|
||||||
|
this._salsa();
|
||||||
|
this._counterIncrement();
|
||||||
|
this.byteCounter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
output[i] = data[i] ^ this.block[this.byteCounter++];
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Encrypt data with key and nonce
|
||||||
|
*
|
||||||
|
* @param {Uint8Array} data
|
||||||
|
* @return {Uint8Array}
|
||||||
|
*/
|
||||||
|
Salsa20.prototype.encrypt = function (data) {
|
||||||
|
return this._update(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt data with key and nonce
|
||||||
|
*
|
||||||
|
* @param {Uint8Array} data
|
||||||
|
* @return {Uint8Array}
|
||||||
|
*/
|
||||||
|
Salsa20.prototype.decrypt = function (data) {
|
||||||
|
return this._update(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
Salsa20.prototype._counterIncrement = function () {
|
||||||
|
// Max possible blocks is 2^64
|
||||||
|
this.param[8] = (this.param[8] + 1) >>> 0;
|
||||||
|
if (this.param[8] === 0) {
|
||||||
|
this.param[9] = (this.param[9] + 1) >>> 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Salsa20.prototype._salsa = function () {
|
||||||
|
const mix = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||||
|
let i = 0;
|
||||||
|
let b = 0;
|
||||||
|
|
||||||
|
const qr = function (a, b, c, d) {
|
||||||
|
mix[b] ^= _rotl(mix[a] + mix[d], 7) >>> 0;
|
||||||
|
mix[c] ^= _rotl(mix[b] + mix[a], 9) >>> 0;
|
||||||
|
mix[d] ^= _rotl(mix[c] + mix[b], 13) >>> 0;
|
||||||
|
mix[a] ^= _rotl(mix[d] + mix[c], 18) >>> 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// copy param array to mix //
|
||||||
|
for (i = 0; i < 16; i++) {
|
||||||
|
mix[i] = this.param[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// mix rounds //
|
||||||
|
for (i = 0; i < this.rounds; i += 2) {
|
||||||
|
qr(0, 4, 8, 12);
|
||||||
|
qr(5, 9, 13, 1);
|
||||||
|
qr(10, 14, 2, 6);
|
||||||
|
qr(15, 3, 7, 11);
|
||||||
|
qr(0, 1, 2, 3);
|
||||||
|
qr(5, 6, 7, 4);
|
||||||
|
qr(10, 11, 8, 9);
|
||||||
|
qr(15, 12, 13, 14);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < 16; i++) {
|
||||||
|
// add
|
||||||
|
mix[i] += this.param[i];
|
||||||
|
|
||||||
|
// store
|
||||||
|
this.block[b++] = mix[i] & 0xFF;
|
||||||
|
this.block[b++] = (mix[i] >>> 8) & 0xFF;
|
||||||
|
this.block[b++] = (mix[i] >>> 16) & 0xFF;
|
||||||
|
this.block[b++] = (mix[i] >>> 24) & 0xFF;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Little-endian to uint 32 bytes
|
||||||
|
*
|
||||||
|
* @param {Uint8Array|[number]} data
|
||||||
|
* @param {number} index
|
||||||
|
* @return {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const _get32 = function (data, index) {
|
||||||
|
return data[index++] ^ (data[index++] << 8) ^ (data[index++] << 16) ^ (data[index] << 24);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cyclic left rotation
|
||||||
|
*
|
||||||
|
* @param {number} data
|
||||||
|
* @param {number} shift
|
||||||
|
* @return {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const _rotl = function (data, shift) {
|
||||||
|
return ((data << shift) | (data >>> (32 - shift)));
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Salsa20
|
||||||
|
;
|
90
src/core/operations/Salsa20Decrypt.mjs
Normal file
90
src/core/operations/Salsa20Decrypt.mjs
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
/**
|
||||||
|
* @author cbeuw [cbeuw.andy@gmail.com]
|
||||||
|
* @copyright Crown Copyright 2020
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
|
import Salsa20 from "../lib/Salsa20";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Salsa20 Decrypt operation
|
||||||
|
*/
|
||||||
|
class Salsa20Decrypt extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Salsa20Decrypt constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Salsa20 Decrypt";
|
||||||
|
this.module = "Crypto";
|
||||||
|
this.description = "Salsa20 is a stream cipher developed by Daniel Bernstein in 2005.<br><br><b>Key:</b> Key length should be 16 or 32 bytes (128 or 256 bits).<br><br><b>Nonce:</b> The one-time nonce should be 8 bytes long.";
|
||||||
|
this.infoURL = "https://en.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"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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),
|
||||||
|
nonce = Utils.convertToByteArray(args[1].string, args[1].option),
|
||||||
|
inputType = args[2],
|
||||||
|
outputType = args[3];
|
||||||
|
|
||||||
|
if (key.length !== 16 && key.length !== 32) {
|
||||||
|
throw new OperationError(`Invalid key length: ${key.length} bytes
|
||||||
|
|
||||||
|
Salsa20 requires a key length of either 16 bytes or 32 bytes`);
|
||||||
|
}
|
||||||
|
if (nonce.length !== 8) {
|
||||||
|
throw new OperationError(`Invalid nonce length: ${nonce.length} bytes
|
||||||
|
|
||||||
|
Salsa20 requires a nonce length of 8 bytes`);
|
||||||
|
}
|
||||||
|
input = Utils.convertToByteArray(input, inputType);
|
||||||
|
|
||||||
|
const cipher = new Salsa20(key, nonce);
|
||||||
|
const output = cipher.decrypt(input);
|
||||||
|
|
||||||
|
if (outputType === "Hex") {
|
||||||
|
return Buffer.from(output).toString("hex");
|
||||||
|
} else {
|
||||||
|
return Buffer.from(output).toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Salsa20Decrypt;
|
89
src/core/operations/Salsa20Encrypt.mjs
Normal file
89
src/core/operations/Salsa20Encrypt.mjs
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
/**
|
||||||
|
* @author cbeuw [cbeuw.andy@gmail.com]
|
||||||
|
* @copyright Crown Copyright 2020
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
|
import Salsa20 from "../lib/Salsa20";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Salsa20 Encrypt operation
|
||||||
|
*/
|
||||||
|
class Salsa20Encrypt extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Salsa20Encrypt constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Salsa20 Encrypt";
|
||||||
|
this.module = "Crypto";
|
||||||
|
this.description = "Salsa20 is a stream cipher developed by Daniel Bernstein in 2005.<br><br><b>Key:</b> Key length should be 16 or 32 bytes (128 or 256 bits).<br><br><b>Nonce:</b> The one-time nonce should be 8 bytes long.";
|
||||||
|
this.infoURL = "https://en.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"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Input",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["Raw", "Hex"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Output",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["Hex", "Raw"]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const key = Utils.convertToByteArray(args[0].string, args[0].option),
|
||||||
|
nonce = Utils.convertToByteArray(args[1].string, args[1].option),
|
||||||
|
inputType = args[2],
|
||||||
|
outputType = args[3];
|
||||||
|
|
||||||
|
if (key.length !== 16 && key.length !== 32) {
|
||||||
|
throw new OperationError(`Invalid key length: ${key.length} bytes
|
||||||
|
|
||||||
|
Salsa20 requires a key length of either 16 bytes or 32 bytes`);
|
||||||
|
}
|
||||||
|
if (nonce.length !== 8) {
|
||||||
|
throw new OperationError(`Invalid nonce length: ${nonce.length} bytes
|
||||||
|
|
||||||
|
Salsa20 requires a nonce length of 8 bytes`);
|
||||||
|
}
|
||||||
|
input = Utils.convertToByteArray(input, inputType);
|
||||||
|
|
||||||
|
const cipher = new Salsa20(key, nonce);
|
||||||
|
const output = cipher.encrypt(input);
|
||||||
|
|
||||||
|
if (outputType === "Hex") {
|
||||||
|
return Buffer.from(output).toString("hex");
|
||||||
|
} else {
|
||||||
|
return Buffer.from(output).toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Salsa20Encrypt;
|
Loading…
Add table
Add a link
Reference in a new issue