js-salsa20
Version:
Pure JavaScript Salsa20 stream cipher
247 lines (226 loc) • 8.22 kB
JavaScript
'use strict'
/*
* Copyright (c) 2017, Bubelich Mykola
* https://www.bubelich.com
*
* (。◕‿‿◕。)
*
* 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 {Uint8Array} key
* @param {Uint8Array} nonce
*/
var JSSalsa20 = function (key, nonce) {
if (!(key instanceof Uint8Array) || key.length !== 32) {
throw new Error('Key should be 32 byte array!')
}
if (!(nonce instanceof Uint8Array) || nonce.length !== 8) {
throw new Error('Nonce should be 8 byte array!')
}
this.rounds = 20
this.sigma = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574]
this.param = [
// Constant
this.sigma[0],
// Key
this._get32(key, 0),
this._get32(key, 4),
this._get32(key, 8),
this._get32(key, 12),
this.sigma[1],
// Nonce
this._get32(nonce, 0),
this._get32(nonce, 4),
// Counter
0,
0,
// Constant
this.sigma[2],
// Key
this._get32(key, 16),
this._get32(key, 20),
this._get32(key, 24),
this._get32(key, 28),
// Const
this.sigma[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 {Uint8Array} data
* @return {Uint8Array}
* @private
*/
JSSalsa20.prototype._update = function (data) {
if (!(data instanceof Uint8Array) || data.length === 0) {
throw new Error('Data should be type of bytes (Uint8Array) and not empty!')
}
var output = new Uint8Array(data.length)
// core function, build block and xor with input data //
for (var 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}
*/
JSSalsa20.prototype.encrypt = function (data) {
return this._update(data)
}
/**
* Decrypt data with key and nonce
*
* @param {Uint8Array} data
* @return {Uint8Array}
*/
JSSalsa20.prototype.decrypt = function (data) {
return this._update(data)
}
JSSalsa20.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
}
}
JSSalsa20.prototype._salsa = function () {
var mix = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
var i = 0
var b = 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) {
mix[4] = (mix[4] ^ this._rotl(mix[0] + mix[12], 7)) >>> 0
mix[8] = (mix[8] ^ this._rotl(mix[4] + mix[0], 9)) >>> 0
mix[12] = (mix[12] ^ this._rotl(mix[8] + mix[4], 13)) >>> 0
mix[0] = (mix[0] ^ this._rotl(mix[12] + mix[8], 18)) >>> 0
mix[9] = (mix[9] ^ this._rotl(mix[5] + mix[1], 7)) >>> 0
mix[13] = (mix[13] ^ this._rotl(mix[9] + mix[5], 9)) >>> 0
mix[1] = (mix[1] ^ this._rotl(mix[13] + mix[9], 13)) >>> 0
mix[5] = (mix[5] ^ this._rotl(mix[1] + mix[13], 18)) >>> 0
mix[14] = (mix[14] ^ this._rotl(mix[10] + mix[6], 7)) >>> 0
mix[2] = (mix[2] ^ this._rotl(mix[14] + mix[10], 9)) >>> 0
mix[6] = (mix[6] ^ this._rotl(mix[2] + mix[14], 13)) >>> 0
mix[10] = (mix[10] ^ this._rotl(mix[6] + mix[2], 18)) >>> 0
mix[3] = (mix[3] ^ this._rotl(mix[15] + mix[11], 7)) >>> 0
mix[7] = (mix[7] ^ this._rotl(mix[3] + mix[15], 9)) >>> 0
mix[11] = (mix[11] ^ this._rotl(mix[7] + mix[3], 13)) >>> 0
mix[15] = (mix[15] ^ this._rotl(mix[11] + mix[7], 18)) >>> 0
mix[1] = (mix[1] ^ this._rotl(mix[0] + mix[3], 7)) >>> 0
mix[2] = (mix[2] ^ this._rotl(mix[1] + mix[0], 9)) >>> 0
mix[3] = (mix[3] ^ this._rotl(mix[2] + mix[1], 13)) >>> 0
mix[0] = (mix[0] ^ this._rotl(mix[3] + mix[2], 18)) >>> 0
mix[6] = (mix[6] ^ this._rotl(mix[5] + mix[4], 7)) >>> 0
mix[7] = (mix[7] ^ this._rotl(mix[6] + mix[5], 9)) >>> 0
mix[4] = (mix[4] ^ this._rotl(mix[7] + mix[6], 13)) >>> 0
mix[5] = (mix[5] ^ this._rotl(mix[4] + mix[7], 18)) >>> 0
mix[11] = (mix[11] ^ this._rotl(mix[10] + mix[9], 7)) >>> 0
mix[8] = (mix[8] ^ this._rotl(mix[11] + mix[10], 9)) >>> 0
mix[9] = (mix[9] ^ this._rotl(mix[8] + mix[11], 13)) >>> 0
mix[10] = (mix[10] ^ this._rotl(mix[9] + mix[8], 18)) >>> 0
mix[12] = (mix[12] ^ this._rotl(mix[15] + mix[14], 7)) >>> 0
mix[13] = (mix[13] ^ this._rotl(mix[12] + mix[15], 9)) >>> 0
mix[14] = (mix[14] ^ this._rotl(mix[13] + mix[12], 13)) >>> 0
mix[15] = (mix[15] ^ this._rotl(mix[14] + mix[13], 18)) >>> 0
}
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
*/
JSSalsa20.prototype._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
*/
JSSalsa20.prototype._rotl = function (data, shift) {
return ((data << shift) | (data >>> (32 - shift)))
}
// EXPORT //
if (typeof module !== 'undefined' && module.exports) {
module.exports = JSSalsa20
}