diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json
index 53ca796d..8b0ed081 100755
--- a/src/core/config/Categories.json
+++ b/src/core/config/Categories.json
@@ -79,6 +79,8 @@
"RC2 Decrypt",
"RC4",
"RC4 Drop",
+ "Salsa20 Encrypt",
+ "Salsa20 Decrypt",
"ROT13",
"ROT47",
"XOR",
diff --git a/src/core/lib/Salsa20.mjs b/src/core/lib/Salsa20.mjs
new file mode 100644
index 00000000..59d6005b
--- /dev/null
+++ b/src/core/lib/Salsa20.mjs
@@ -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
+;
diff --git a/src/core/operations/Salsa20Decrypt.mjs b/src/core/operations/Salsa20Decrypt.mjs
new file mode 100644
index 00000000..1c23ab36
--- /dev/null
+++ b/src/core/operations/Salsa20Decrypt.mjs
@@ -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.
Key: Key length should be 16 or 32 bytes (128 or 256 bits).
Nonce: 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;
diff --git a/src/core/operations/Salsa20Encrypt.mjs b/src/core/operations/Salsa20Encrypt.mjs
new file mode 100644
index 00000000..18b1ce92
--- /dev/null
+++ b/src/core/operations/Salsa20Encrypt.mjs
@@ -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.
Key: Key length should be 16 or 32 bytes (128 or 256 bits).
Nonce: 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;