Skip to content

@dchest /salsa20.js
Last active

Embed URL

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
var Salsa20 = (function () {
function Salsa20(key, nonce) {
// Constants.
this.rounds = 20; // number of Salsa rounds
this.sigmaWords = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574];
// State.
this.keyWords = []; // key words
this.nonceWords = [0, 0]; // nonce words
this.counterWords = [0, 0]; // block counter words
// Output buffer.
this.block = []; // output block of 64 bytes
this.blockUsed = 64; // number of block bytes used
this.setKey(key);
this.setNonce(nonce);
}
// setKey sets the key to the given 32-byte array.
Salsa20.prototype.setKey = function(key) {
for (var i = 0, j = 0; i < 8; i++, j += 4) {
this.keyWords[i] = (key[j] & 0xff) |
((key[j+1] & 0xff)<<8) |
((key[j+2] & 0xff)<<16) |
((key[j+3] & 0xff)<<24);
}
this._reset();
};
// setNonce sets the nonce to the given 8-byte array.
Salsa20.prototype.setNonce = function(nonce) {
this.nonceWords[0] = (nonce[0] & 0xff) |
((nonce[1] & 0xff)<<8) |
((nonce[2] & 0xff)<<16) |
((nonce[3] & 0xff)<<24);
this.nonceWords[1] = (nonce[4] & 0xff) |
((nonce[5] & 0xff)<<8) |
((nonce[6] & 0xff)<<16) |
((nonce[7] & 0xff)<<24);
this._reset();
};
// getBytes returns the next numberOfBytes bytes of stream.
Salsa20.prototype.getBytes = function(numberOfBytes) {
var out = new Array(numberOfBytes);
for (var i = 0; i < numberOfBytes; i++) {
if (this.blockUsed == 64) {
this._generateBlock();
this._incrementCounter();
this.blockUsed = 0;
}
out[i] = this.block[this.blockUsed];
this.blockUsed++;
}
return out;
};
Salsa20.prototype.getHexString = function(numberOfBytes) {
var hex=['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'];
var out = [];
var bytes = this.getBytes(numberOfBytes);
for(var i = 0; i < bytes.length; i++) {
out.push(hex[(bytes[i] >> 4) & 15]);
out.push(hex[bytes[i] & 15]);
}
return out.join('');
};
// Private methods.
Salsa20.prototype._reset = function() {
this.counterWords[0] = 0;
this.counterWords[1] = 0;
this.blockUsed = 64;
};
// _incrementCounter increments block counter.
Salsa20.prototype._incrementCounter = function() {
// Note: maximum 2^64 blocks.
this.counterWords[0] = (this.counterWords[0] + 1) & 0xffffffff;
if (this.counterWords[0] == 0) {
this.counterWords[1] = (this.counterWords[1] + 1) & 0xffffffff;
}
};
// _generateBlock generates 64 bytes from key, nonce, and counter,
// and puts the result into this.block.
Salsa20.prototype._generateBlock = function() {
var j0 = this.sigmaWords[0],
j1 = this.keyWords[0],
j2 = this.keyWords[1],
j3 = this.keyWords[2],
j4 = this.keyWords[3],
j5 = this.sigmaWords[1],
j6 = this.nonceWords[0],
j7 = this.nonceWords[1],
j8 = this.counterWords[0],
j9 = this.counterWords[1],
j10 = this.sigmaWords[2],
j11 = this.keyWords[4],
j12 = this.keyWords[5],
j13 = this.keyWords[6],
j14 = this.keyWords[7],
j15 = this.sigmaWords[3];
var x0 = j0, x1 = j1, x2 = j2, x3 = j3, x4 = j4, x5 = j5, x6 = j6, x7 = j7,
x8 = j8, x9 = j9, x10 = j10, x11 = j11, x12 = j12, x13 = j13, x14 = j14, x15 = j15;
var u;
for (var i = 0; i < this.rounds; i += 2) {
u = x0 + x12;
x4 ^= (u<<7) | (u>>>(32-7));
u = x4 + x0;
x8 ^= (u<<9) | (u>>>(32-9));
u = x8 + x4;
x12 ^= (u<<13) | (u>>>(32-13));
u = x12 + x8;
x0 ^= (u<<18) | (u>>>(32-18));
u = x5 + x1;
x9 ^= (u<<7) | (u>>>(32-7));
u = x9 + x5;
x13 ^= (u<<9) | (u>>>(32-9));
u = x13 + x9;
x1 ^= (u<<13) | (u>>>(32-13));
u = x1 + x13;
x5 ^= (u<<18) | (u>>>(32-18));
u = x10 + x6;
x14 ^= (u<<7) | (u>>>(32-7));
u = x14 + x10;
x2 ^= (u<<9) | (u>>>(32-9));
u = x2 + x14;
x6 ^= (u<<13) | (u>>>(32-13));
u = x6 + x2;
x10 ^= (u<<18) | (u>>>(32-18));
u = x15 + x11;
x3 ^= (u<<7) | (u>>>(32-7));
u = x3 + x15;
x7 ^= (u<<9) | (u>>>(32-9));
u = x7 + x3;
x11 ^= (u<<13) | (u>>>(32-13));
u = x11 + x7;
x15 ^= (u<<18) | (u>>>(32-18));
u = x0 + x3;
x1 ^= (u<<7) | (u>>>(32-7));
u = x1 + x0;
x2 ^= (u<<9) | (u>>>(32-9));
u = x2 + x1;
x3 ^= (u<<13) | (u>>>(32-13));
u = x3 + x2;
x0 ^= (u<<18) | (u>>>(32-18));
u = x5 + x4;
x6 ^= (u<<7) | (u>>>(32-7));
u = x6 + x5;
x7 ^= (u<<9) | (u>>>(32-9));
u = x7 + x6;
x4 ^= (u<<13) | (u>>>(32-13));
u = x4 + x7;
x5 ^= (u<<18) | (u>>>(32-18));
u = x10 + x9;
x11 ^= (u<<7) | (u>>>(32-7));
u = x11 + x10;
x8 ^= (u<<9) | (u>>>(32-9));
u = x8 + x11;
x9 ^= (u<<13) | (u>>>(32-13));
u = x9 + x8;
x10 ^= (u<<18) | (u>>>(32-18));
u = x15 + x14;
x12 ^= (u<<7) | (u>>>(32-7));
u = x12 + x15;
x13 ^= (u<<9) | (u>>>(32-9));
u = x13 + x12;
x14 ^= (u<<13) | (u>>>(32-13));
u = x14 + x13;
x15 ^= (u<<18) | (u>>>(32-18));
}
x0 += j0;
x1 += j1;
x2 += j2;
x3 += j3;
x4 += j4;
x5 += j5;
x6 += j6;
x7 += j7;
x8 += j8;
x9 += j9;
x10 += j10;
x11 += j11;
x12 += j12;
x13 += j13;
x14 += j14;
x15 += j15;
this.block[ 0] = ( x0 >>> 0) & 0xff; this.block[ 1] = ( x0 >>> 8) & 0xff;
this.block[ 2] = ( x0 >>> 16) & 0xff; this.block[ 3] = ( x0 >>> 24) & 0xff;
this.block[ 4] = ( x1 >>> 0) & 0xff; this.block[ 5] = ( x1 >>> 8) & 0xff;
this.block[ 6] = ( x1 >>> 16) & 0xff; this.block[ 7] = ( x1 >>> 24) & 0xff;
this.block[ 8] = ( x2 >>> 0) & 0xff; this.block[ 9] = ( x2 >>> 8) & 0xff;
this.block[10] = ( x2 >>> 16) & 0xff; this.block[11] = ( x2 >>> 24) & 0xff;
this.block[12] = ( x3 >>> 0) & 0xff; this.block[13] = ( x3 >>> 8) & 0xff;
this.block[14] = ( x3 >>> 16) & 0xff; this.block[15] = ( x3 >>> 24) & 0xff;
this.block[16] = ( x4 >>> 0) & 0xff; this.block[17] = ( x4 >>> 8) & 0xff;
this.block[18] = ( x4 >>> 16) & 0xff; this.block[19] = ( x4 >>> 24) & 0xff;
this.block[20] = ( x5 >>> 0) & 0xff; this.block[21] = ( x5 >>> 8) & 0xff;
this.block[22] = ( x5 >>> 16) & 0xff; this.block[23] = ( x5 >>> 24) & 0xff;
this.block[24] = ( x6 >>> 0) & 0xff; this.block[25] = ( x6 >>> 8) & 0xff;
this.block[26] = ( x6 >>> 16) & 0xff; this.block[27] = ( x6 >>> 24) & 0xff;
this.block[28] = ( x7 >>> 0) & 0xff; this.block[29] = ( x7 >>> 8) & 0xff;
this.block[30] = ( x7 >>> 16) & 0xff; this.block[31] = ( x7 >>> 24) & 0xff;
this.block[32] = ( x8 >>> 0) & 0xff; this.block[33] = ( x8 >>> 8) & 0xff;
this.block[34] = ( x8 >>> 16) & 0xff; this.block[35] = ( x8 >>> 24) & 0xff;
this.block[36] = ( x9 >>> 0) & 0xff; this.block[37] = ( x9 >>> 8) & 0xff;
this.block[38] = ( x9 >>> 16) & 0xff; this.block[39] = ( x9 >>> 24) & 0xff;
this.block[40] = (x10 >>> 0) & 0xff; this.block[41] = (x10 >>> 8) & 0xff;
this.block[42] = (x10 >>> 16) & 0xff; this.block[43] = (x10 >>> 24) & 0xff;
this.block[44] = (x11 >>> 0) & 0xff; this.block[45] = (x11 >>> 8) & 0xff;
this.block[46] = (x11 >>> 16) & 0xff; this.block[47] = (x11 >>> 24) & 0xff;
this.block[48] = (x12 >>> 0) & 0xff; this.block[49] = (x12 >>> 8) & 0xff;
this.block[50] = (x12 >>> 16) & 0xff; this.block[51] = (x12 >>> 24) & 0xff;
this.block[52] = (x13 >>> 0) & 0xff; this.block[53] = (x13 >>> 8) & 0xff;
this.block[54] = (x13 >>> 16) & 0xff; this.block[55] = (x13 >>> 24) & 0xff;
this.block[56] = (x14 >>> 0) & 0xff; this.block[57] = (x14 >>> 8) & 0xff;
this.block[58] = (x14 >>> 16) & 0xff; this.block[59] = (x14 >>> 24) & 0xff;
this.block[60] = (x15 >>> 0) & 0xff; this.block[61] = (x15 >>> 8) & 0xff;
this.block[62] = (x15 >>> 16) & 0xff; this.block[63] = (x15 >>> 24) & 0xff;
};
return Salsa20;
})();
// ---------- Test -------------
var key = [0x80]; for (i = 1; i < 32; i++) key[i] = 0;
var nonce = []; for (i = 0; i < 8; i++) nonce[i] = 0;
var good = [
// 0..63
"e3be8fdd8beca2e3ea8ef9475b29a6e7" +
"003951e1097a5c38d23b7a5fad9f6844" +
"b22c97559e2723c7cbbd3fe4fc8d9a07" +
"44652a83e72a9c461876af4d7ef1a117",
// 192..255
"57be81f47b17d9ae7c4ff15429a73e10" +
"acf250ed3a90a93c711308a74c6216a9" +
"ed84cd126da7f28e8abf8bb63517e1ca" +
"98e712f4fb2e1a6aed9fdc73291faa17",
// 256..319
"958211c4ba2ebd5838c635edb81f513a" +
"91a294e194f1c039aeec657dce40aa7e" +
"7c0af57cacefa40c9f14b71a4b3456a6" +
"3e162ec7d8d10b8ffb1810d71001b618",
// 448..511
"696afcfd0cddcc83c7e77f11a649d79a" +
"cdc3354e9635ff137e929933a0bd6f53" +
"77efa105a3a4266b7c0d089d08f1e855" +
"cc32b15b93784a36e56a76cc64bc8477"
];
var state = new Salsa20(key, nonce);
// compare 0..63
if (state.getHexString(64) != good[0])
console.log("BAD 0..63");
// discard 64..191
state.getBytes(128);
// compare 192..255
if (state.getHexString(64) != good[1])
console.log("BAD 192..255");
// compare 256..319
if (state.getHexString(64) != good[2])
console.log("BAD 256..319");
// discard 320..447
state.getBytes(128);
// compare 448..511
if (state.getHexString(64) != good[3])
console.log("BAD 448..511");
console.log("done");
@DukeyToo

If someone wants to use this to decrypt/encrypt data, the following might help:

 Salsa20.prototype.decrypt = function(inBytes) {
      var outBytes = new Array(inBytes.length);
      var keyBytes = this.getBytes(inBytes.length);
      for(var i=0; i<inBytes.length; i++) {
        outBytes[i] = keyBytes[i] ^ inBytes[i];
      }

      return outBytes;
    };

    Salsa20.prototype.encrypt = Salsa20.prototype.decrypt;  //yep, its the same both ways due to the xor operation
@asdri

Hi @DukeyToo can you tell me how to use Salsa20.encrypt() ? I have tried Salsa20.encrypt("hello") but console outputs TypeError: Salsa20.encrypt is not a function. Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.