xchacha20-js
Version:
Javascript implementations of ChaCha20, HChaCha20, and XChaCha20
373 lines (347 loc) • 11.9 kB
JavaScript
"use strict";
const Util = require('./util');
module.exports = class ChaCha20
{
static rotate(v, n)
{
v &= 0xffffffff;
n &= 31;
return (
(
(v << n) | (v >>> (32 - n))
)
) >>> 0;
}
/**
* @param {Number} a
* @param {Number} b
* @param {Number} c
* @param {Number} d
* @return {Number[]}
*/
static quarterRound(a, b, c, d)
{
// a = PLUS(a,b); d = ROTATE(XOR(d,a),16);
a = (a + b) & 0xffffffff;
d = ChaCha20.rotate(d ^ a, 16);
// c = PLUS(c,d); b = ROTATE(XOR(b,c),12);
c = (c + d) & 0xffffffff;
b = ChaCha20.rotate(b ^ c, 12);
// a = PLUS(a,b); d = ROTATE(XOR(d,a), 8);
a = (a + b) & 0xffffffff;
d = ChaCha20.rotate(d ^ a, 8);
// c = PLUS(c,d); b = ROTATE(XOR(b,c), 7);
c = (c + d) & 0xffffffff;
b = ChaCha20.rotate(b ^ c, 7);
return [a >>> 0, b >>> 0, c >>> 0, d >>> 0];
}
/**
* @param {Buffer} key
* @param {Buffer} nonce
* @param {Buffer|Number} counter
* @return {Buffer}
*/
createCtx(key, nonce, counter = null)
{
if (!counter) {
counter = Buffer.alloc(4, 0);
} else if (typeof counter === 'number') {
counter = Util.store32_le(counter);
}
let ctx = Buffer.alloc(64, 0);
const constant = Buffer.from('657870616e642033322d62797465206b', 'hex');
Util.toBuffer(Util.bufferToUint32Array(constant)).copy(ctx, 0, 0, 16);
Util.toBuffer(Util.bufferToUint32LEArray(key)).copy(ctx, 16, 0, 32);
Util.toBuffer(Util.bufferToUint32LEArray(counter)).copy(ctx, 48, 0, 8);
Util.toBuffer(Util.bufferToUint32LEArray(nonce)).copy(ctx, 56, 0, 8);
key.copy(ctx, 16, 0, 32);
counter.copy(ctx, 48, 0, 8);
nonce.copy(ctx, 56, 0, 8);
return ctx;
}
/**
* @param {Buffer} key
* @param {Buffer} nonce
* @param {Buffer|Number} counter
* @return {Buffer}
*/
createIetfCtx(key, nonce, counter = null)
{
if (!counter) {
counter = Buffer.alloc(4, 0);
} else if (typeof counter === 'number') {
counter = Util.store32_le(counter);
}
let ctx = Buffer.alloc(64, 0);
const constant = Buffer.from('657870616e642033322d62797465206b', 'hex');
Util.toBuffer(Util.bufferToUint32Array(constant)).copy(ctx, 0, 0, 16);
Util.toBuffer(Util.bufferToUint32LEArray(key)).copy(ctx, 16, 0, 32);
Util.toBuffer(Util.bufferToUint32LEArray(counter)).copy(ctx, 48, 0, 4);
Util.toBuffer(Util.bufferToUint32LEArray(nonce)).copy(ctx, 52, 0, 12);
return ctx;
}
async encryptBytes(ctx, message)
{
let j0 = ctx.readInt32BE(0) >>> 0;
let j1 = ctx.readInt32BE(1 << 2) >>> 0;
let j2 = ctx.readInt32BE(2 << 2) >>> 0;
let j3 = ctx.readInt32BE(3 << 2) >>> 0; // These 4 are kind of weird.
let j4 = ctx.readInt32LE(4 << 2) >>> 0;
let j5 = ctx.readInt32LE(5 << 2) >>> 0;
let j6 = ctx.readInt32LE(6 << 2) >>> 0;
let j7 = ctx.readInt32LE(7 << 2) >>> 0;
let j8 = ctx.readInt32LE(8 << 2) >>> 0;
let j9 = ctx.readInt32LE(9 << 2) >>> 0;
let j10 = ctx.readInt32LE(10 << 2) >>> 0;
let j11 = ctx.readInt32LE(11 << 2) >>> 0;
let j12 = ctx.readInt32LE(12 << 2) >>> 0;
let j13 = ctx.readInt32LE(13 << 2) >>> 0;
let j14 = ctx.readInt32LE(14 << 2) >>> 0;
let j15 = ctx.readInt32LE(15 << 2) >>> 0;
let x0;
let x1;
let x2;
let x3;
let x4;
let x5;
let x6;
let x7;
let x8;
let x9;
let x10;
let x11;
let x12;
let x13;
let x14;
let x15;
let start = 0;
let end;
let len = message.length;
let cipher = Buffer.alloc(len, 0);
let chunk = Buffer.alloc(64, 0);
while (start < len) {
end = start + 64 >= len ? len : start + 64;
chunk.fill(0, 0);
message.slice(start, end).copy(chunk, 0);
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;
for (let i = 0; i < 10; i++) {
[x0, x4, x8, x12] = ChaCha20.quarterRound(x0, x4, x8, x12);
[x1, x5, x9, x13] = ChaCha20.quarterRound(x1, x5, x9, x13);
[x2, x6, x10, x14] = ChaCha20.quarterRound(x2, x6, x10, x14);
[x3, x7, x11, x15] = ChaCha20.quarterRound(x3, x7, x11, x15);
[x0, x5, x10, x15] = ChaCha20.quarterRound(x0, x5, x10, x15);
[x1, x6, x11, x12] = ChaCha20.quarterRound(x1, x6, x11, x12);
[x2, x7, x8, x13] = ChaCha20.quarterRound(x2, x7, x8, x13);
[x3, x4, x9, x14] = ChaCha20.quarterRound(x3, x4, x9, x14);
}
x0 = this.add(x0, j0);
x1 = this.add(x1, j1);
x2 = this.add(x2, j2);
x3 = this.add(x3, j3);
x4 = this.add(x4, j4);
x5 = this.add(x5, j5);
x6 = this.add(x6, j6);
x7 = this.add(x7, j7);
x8 = this.add(x8, j8);
x9 = this.add(x9, j9);
x10 = this.add(x10, j10);
x11 = this.add(x11, j11);
x12 = this.add(x12, j12);
x13 = this.add(x13, j13);
x14 = this.add(x14, j14);
x15 = this.add(x15, j15);
x0 = this.xor(x0, Util.load32_le(chunk.slice(0, 4)));
x1 = this.xor(x1, Util.load32_le(chunk.slice(4, 8)));
x2 = this.xor(x2, Util.load32_le(chunk.slice(8, 12)));
x3 = this.xor(x3, Util.load32_le(chunk.slice(12, 16)));
x4 = this.xor(x4, Util.load32_le(chunk.slice(16, 20)));
x5 = this.xor(x5, Util.load32_le(chunk.slice(20, 24)));
x6 = this.xor(x6, Util.load32_le(chunk.slice(24, 28)));
x7 = this.xor(x7, Util.load32_le(chunk.slice(28, 32)));
x8 = this.xor(x8, Util.load32_le(chunk.slice(32, 36)));
x9 = this.xor(x9, Util.load32_le(chunk.slice(36, 40)));
x10 = this.xor(x10, Util.load32_le(chunk.slice(40, 44)));
x11 = this.xor(x11, Util.load32_le(chunk.slice(44, 48)));
x12 = this.xor(x12, Util.load32_le(chunk.slice(48, 52)));
x13 = this.xor(x13, Util.load32_le(chunk.slice(52, 56)));
x14 = this.xor(x14, Util.load32_le(chunk.slice(56, 60)));
x15 = this.xor(x15, Util.load32_le(chunk.slice(60, 64)));
Util.store32_le(x0).copy(cipher, start);
Util.store32_le(x1).copy(cipher, start + 4);
Util.store32_le(x2).copy(cipher, start + 8);
Util.store32_le(x3).copy(cipher, start + 12);
Util.store32_le(x4).copy(cipher, start + 16);
Util.store32_le(x5).copy(cipher, start + 20);
Util.store32_le(x6).copy(cipher, start + 24);
Util.store32_le(x7).copy(cipher, start + 28);
Util.store32_le(x8).copy(cipher, start + 32);
Util.store32_le(x9).copy(cipher, start + 36);
Util.store32_le(x10).copy(cipher, start + 40);
Util.store32_le(x11).copy(cipher, start + 44);
Util.store32_le(x12).copy(cipher, start + 48);
Util.store32_le(x13).copy(cipher, start + 52);
Util.store32_le(x14).copy(cipher, start + 56);
Util.store32_le(x15).copy(cipher, start + 60);
j12++;
start += 64;
}
return cipher;
}
/**
* @param {number} a
* @param {number} b
* @return {number}
*/
add(a, b)
{
return ((a + b) & 0xffffffff) >>> 0;
}
/**
* @param {number} a
* @param {number} b
* @return {number}
*/
xor(a, b)
{
return ((a ^ b) & 0xffffffff) >>> 0;
}
/**
*
* @param {Number} len
* @param {Buffer|null} nonce
* @param {Buffer|null} key
* @return {Buffer}
*/
async ietfStream(len = 64, nonce = null, key = null)
{
if (!key) {
key = Buffer.alloc(32, 0);
}
if (!nonce) {
nonce = Buffer.alloc(12, 0);
} else if (nonce.length !== 12) {
throw new Error('Nonce must be 8 bytes')
}
return await this.encryptBytes(
this.createIetfCtx(key, nonce),
Buffer.alloc(len, 0)
);
}
/**
*
* @param {Number} len
* @param {Buffer|null} nonce
* @param {Buffer|null} key
* @param {number} counter
* @return {Buffer}
*/
async ietfStreamIc(len = 64, nonce = null, key = null, counter = 0)
{
if (!key) {
key = Buffer.alloc(32, 0);
}
if (!nonce) {
nonce = Buffer.alloc(12, 0);
} else if (nonce.length !== 12) {
throw new Error('Nonce must be 12 bytes')
}
return await this.encryptBytes(
this.createIetfCtx(key, nonce, counter),
Buffer.alloc(len, 0)
);
}
/**
*
* @param {Number} len
* @param {Buffer|null} nonce
* @param {Buffer|null} key
* @return {Buffer}
*/
async stream(len = 64, nonce = null, key = null)
{
if (!key) {
key = Buffer.alloc(32, 0);
}
if (!nonce) {
nonce = Buffer.alloc(8, 0);
} else if (nonce.length !== 8) {
throw new Error('Nonce must be 8 bytes')
}
return await this.encryptBytes(
this.createCtx(key, nonce),
Buffer.alloc(len, 0)
);
}
/**
*
* @param {Number} len
* @param {Buffer|null} nonce
* @param {Buffer|null} key
* @param {number} counter
* @return {Buffer}
*/
async streamIc(len = 64, nonce = null, key = null, counter = 0)
{
if (!key) {
key = Buffer.alloc(32, 0);
}
if (!nonce) {
nonce = Buffer.alloc(8, 0);
} else if (nonce.length !== 8) {
throw new Error('Nonce must be 8 bytes')
}
return await this.encryptBytes(
this.createCtx(key, nonce, counter),
Buffer.alloc(len, 0)
);
}
/**
*
* @param {string|Buffer} message
* @param {Buffer} nonce
* @param {Buffer} key
* @param {Buffer|Number} ic
*/
async ietfStreamXorIc(message, nonce, key, ic = 0)
{
if (nonce.length !== 12) {
throw new Error('Nonce must be 12 bytes')
}
return await this.encryptBytes(
this.createIetfCtx(key, nonce, ic),
Util.toBuffer(message)
)
}
/**
*
* @param {string|Buffer} message
* @param {Buffer} nonce
* @param {Buffer} key
* @param {Buffer|Number} ic
*/
async streamXorIc(message, nonce, key, ic = 0)
{
if (nonce.length !== 8) {
throw new Error('Nonce must be 8 bytes')
}
return this.encryptBytes(
this.createCtx(key, nonce, ic),
Util.toBuffer(message)
)
}
};