221 lines
8.5 KiB
TypeScript
221 lines
8.5 KiB
TypeScript
// Copyright (C) 2016 Dmitry Chestnykh
|
|
// MIT License. See LICENSE file for details.
|
|
|
|
/**
|
|
* Package chacha implements ChaCha stream cipher.
|
|
*/
|
|
|
|
import { writeUint32LE } from "@stablelib/binary";
|
|
import { wipe } from "@stablelib/wipe";
|
|
|
|
// Number of ChaCha rounds (ChaCha20).
|
|
const ROUNDS = 20;
|
|
|
|
// Applies the ChaCha core function to 16-byte input,
|
|
// 32-byte key key, and puts the result into 64-byte array out.
|
|
function core(out: Uint8Array, input: Uint8Array, key: Uint8Array): void {
|
|
let j0 = 0x61707865; // "expa" -- ChaCha's "sigma" constant
|
|
let j1 = 0x3320646E; // "nd 3" for 32-byte keys
|
|
let j2 = 0x79622D32; // "2-by"
|
|
let j3 = 0x6B206574; // "te k"
|
|
let j4 = (key[3] << 24) | (key[2] << 16) | (key[1] << 8) | key[0];
|
|
let j5 = (key[7] << 24) | (key[6] << 16) | (key[5] << 8) | key[4];
|
|
let j6 = (key[11] << 24) | (key[10] << 16) | (key[9] << 8) | key[8];
|
|
let j7 = (key[15] << 24) | (key[14] << 16) | (key[13] << 8) | key[12];
|
|
let j8 = (key[19] << 24) | (key[18] << 16) | (key[17] << 8) | key[16];
|
|
let j9 = (key[23] << 24) | (key[22] << 16) | (key[21] << 8) | key[20];
|
|
let j10 = (key[27] << 24) | (key[26] << 16) | (key[25] << 8) | key[24];
|
|
let j11 = (key[31] << 24) | (key[30] << 16) | (key[29] << 8) | key[28];
|
|
let j12 = (input[3] << 24) | (input[2] << 16) | (input[1] << 8) | input[0];
|
|
let j13 = (input[7] << 24) | (input[6] << 16) | (input[5] << 8) | input[4];
|
|
let j14 = (input[11] << 24) | (input[10] << 16) | (input[9] << 8) | input[8];
|
|
let j15 = (input[15] << 24) | (input[14] << 16) | (input[13] << 8) | input[12];
|
|
|
|
let x0 = j0;
|
|
let x1 = j1;
|
|
let x2 = j2;
|
|
let x3 = j3;
|
|
let x4 = j4;
|
|
let x5 = j5;
|
|
let x6 = j6;
|
|
let x7 = j7;
|
|
let x8 = j8;
|
|
let x9 = j9;
|
|
let x10 = j10;
|
|
let x11 = j11;
|
|
let x12 = j12;
|
|
let x13 = j13;
|
|
let x14 = j14;
|
|
let x15 = j15;
|
|
|
|
for (let i = 0; i < ROUNDS; i += 2) {
|
|
x0 = x0 + x4 | 0; x12 ^= x0; x12 = x12 >>> (32 - 16) | x12 << 16;
|
|
x8 = x8 + x12 | 0; x4 ^= x8; x4 = x4 >>> (32 - 12) | x4 << 12;
|
|
x1 = x1 + x5 | 0; x13 ^= x1; x13 = x13 >>> (32 - 16) | x13 << 16;
|
|
x9 = x9 + x13 | 0; x5 ^= x9; x5 = x5 >>> (32 - 12) | x5 << 12;
|
|
|
|
x2 = x2 + x6 | 0; x14 ^= x2; x14 = x14 >>> (32 - 16) | x14 << 16;
|
|
x10 = x10 + x14 | 0; x6 ^= x10; x6 = x6 >>> (32 - 12) | x6 << 12;
|
|
x3 = x3 + x7 | 0; x15 ^= x3; x15 = x15 >>> (32 - 16) | x15 << 16;
|
|
x11 = x11 + x15 | 0; x7 ^= x11; x7 = x7 >>> (32 - 12) | x7 << 12;
|
|
|
|
x2 = x2 + x6 | 0; x14 ^= x2; x14 = x14 >>> (32 - 8) | x14 << 8;
|
|
x10 = x10 + x14 | 0; x6 ^= x10; x6 = x6 >>> (32 - 7) | x6 << 7;
|
|
x3 = x3 + x7 | 0; x15 ^= x3; x15 = x15 >>> (32 - 8) | x15 << 8;
|
|
x11 = x11 + x15 | 0; x7 ^= x11; x7 = x7 >>> (32 - 7) | x7 << 7;
|
|
|
|
x1 = x1 + x5 | 0; x13 ^= x1; x13 = x13 >>> (32 - 8) | x13 << 8;
|
|
x9 = x9 + x13 | 0; x5 ^= x9; x5 = x5 >>> (32 - 7) | x5 << 7;
|
|
x0 = x0 + x4 | 0; x12 ^= x0; x12 = x12 >>> (32 - 8) | x12 << 8;
|
|
x8 = x8 + x12 | 0; x4 ^= x8; x4 = x4 >>> (32 - 7) | x4 << 7;
|
|
|
|
x0 = x0 + x5 | 0; x15 ^= x0; x15 = x15 >>> (32 - 16) | x15 << 16;
|
|
x10 = x10 + x15 | 0; x5 ^= x10; x5 = x5 >>> (32 - 12) | x5 << 12;
|
|
x1 = x1 + x6 | 0; x12 ^= x1; x12 = x12 >>> (32 - 16) | x12 << 16;
|
|
x11 = x11 + x12 | 0; x6 ^= x11; x6 = x6 >>> (32 - 12) | x6 << 12;
|
|
|
|
x2 = x2 + x7 | 0; x13 ^= x2; x13 = x13 >>> (32 - 16) | x13 << 16;
|
|
x8 = x8 + x13 | 0; x7 ^= x8; x7 = x7 >>> (32 - 12) | x7 << 12;
|
|
x3 = x3 + x4 | 0; x14 ^= x3; x14 = x14 >>> (32 - 16) | x14 << 16;
|
|
x9 = x9 + x14 | 0; x4 ^= x9; x4 = x4 >>> (32 - 12) | x4 << 12;
|
|
|
|
x2 = x2 + x7 | 0; x13 ^= x2; x13 = x13 >>> (32 - 8) | x13 << 8;
|
|
x8 = x8 + x13 | 0; x7 ^= x8; x7 = x7 >>> (32 - 7) | x7 << 7;
|
|
x3 = x3 + x4 | 0; x14 ^= x3; x14 = x14 >>> (32 - 8) | x14 << 8;
|
|
x9 = x9 + x14 | 0; x4 ^= x9; x4 = x4 >>> (32 - 7) | x4 << 7;
|
|
|
|
x1 = x1 + x6 | 0; x12 ^= x1; x12 = x12 >>> (32 - 8) | x12 << 8;
|
|
x11 = x11 + x12 | 0; x6 ^= x11; x6 = x6 >>> (32 - 7) | x6 << 7;
|
|
x0 = x0 + x5 | 0; x15 ^= x0; x15 = x15 >>> (32 - 8) | x15 << 8;
|
|
x10 = x10 + x15 | 0; x5 ^= x10; x5 = x5 >>> (32 - 7) | x5 << 7;
|
|
}
|
|
writeUint32LE(x0 + j0 | 0, out, 0);
|
|
writeUint32LE(x1 + j1 | 0, out, 4);
|
|
writeUint32LE(x2 + j2 | 0, out, 8);
|
|
writeUint32LE(x3 + j3 | 0, out, 12);
|
|
writeUint32LE(x4 + j4 | 0, out, 16);
|
|
writeUint32LE(x5 + j5 | 0, out, 20);
|
|
writeUint32LE(x6 + j6 | 0, out, 24);
|
|
writeUint32LE(x7 + j7 | 0, out, 28);
|
|
writeUint32LE(x8 + j8 | 0, out, 32);
|
|
writeUint32LE(x9 + j9 | 0, out, 36);
|
|
writeUint32LE(x10 + j10 | 0, out, 40);
|
|
writeUint32LE(x11 + j11 | 0, out, 44);
|
|
writeUint32LE(x12 + j12 | 0, out, 48);
|
|
writeUint32LE(x13 + j13 | 0, out, 52);
|
|
writeUint32LE(x14 + j14 | 0, out, 56);
|
|
writeUint32LE(x15 + j15 | 0, out, 60);
|
|
}
|
|
|
|
/**
|
|
* Encrypt src with ChaCha20 stream generated for the given 32-byte key and
|
|
* 8-byte (as in original implementation) or 12-byte (as in RFC7539) nonce and
|
|
* write the result into dst and return it.
|
|
*
|
|
* dst and src may be the same, but otherwise must not overlap.
|
|
*
|
|
* If nonce is 12 bytes, users should not encrypt more than 256 GiB with the
|
|
* same key and nonce, otherwise the stream will repeat. The function will
|
|
* throw error if counter overflows to prevent this.
|
|
*
|
|
* If nonce is 8 bytes, the output is practically unlimited (2^70 bytes, which
|
|
* is more than a million petabytes). However, it is not recommended to
|
|
* generate 8-byte nonces randomly, as the chance of collision is high.
|
|
*
|
|
* Never use the same key and nonce to encrypt more than one message.
|
|
*
|
|
* If nonceInplaceCounterLength is not 0, the nonce is assumed to be a 16-byte
|
|
* array with stream counter in first nonceInplaceCounterLength bytes and nonce
|
|
* in the last remaining bytes. The counter will be incremented inplace for
|
|
* each ChaCha block. This is useful if you need to encrypt one stream of data
|
|
* in chunks.
|
|
*/
|
|
export function streamXOR(key: Uint8Array, nonce: Uint8Array,
|
|
src: Uint8Array, dst: Uint8Array, nonceInplaceCounterLength = 0): Uint8Array {
|
|
// We only support 256-bit keys.
|
|
if (key.length !== 32) {
|
|
throw new Error("ChaCha: key size must be 32 bytes");
|
|
}
|
|
|
|
if (dst.length < src.length) {
|
|
throw new Error("ChaCha: destination is shorter than source");
|
|
}
|
|
|
|
let nc: Uint8Array;
|
|
let counterLength: number;
|
|
|
|
if (nonceInplaceCounterLength === 0) {
|
|
if (nonce.length !== 8 && nonce.length !== 12) {
|
|
throw new Error("ChaCha nonce must be 8 or 12 bytes");
|
|
}
|
|
nc = new Uint8Array(16);
|
|
// First counterLength bytes of nc are counter, starting with zero.
|
|
counterLength = nc.length - nonce.length;
|
|
// Last bytes of nc after counterLength are nonce, set them.
|
|
nc.set(nonce, counterLength);
|
|
} else {
|
|
if (nonce.length !== 16) {
|
|
throw new Error("ChaCha nonce with counter must be 16 bytes");
|
|
}
|
|
// This will update passed nonce with counter inplace.
|
|
nc = nonce;
|
|
counterLength = nonceInplaceCounterLength;
|
|
}
|
|
|
|
// Allocate temporary space for ChaCha block.
|
|
const block = new Uint8Array(64);
|
|
|
|
for (let i = 0; i < src.length; i += 64) {
|
|
// Generate a block.
|
|
core(block, nc, key);
|
|
|
|
// XOR block bytes with src into dst.
|
|
for (let j = i; j < i + 64 && j < src.length; j++) {
|
|
dst[j] = src[j] ^ block[j - i];
|
|
}
|
|
|
|
// Increment counter.
|
|
incrementCounter(nc, 0, counterLength);
|
|
}
|
|
|
|
// Cleanup temporary space.
|
|
wipe(block);
|
|
|
|
if (nonceInplaceCounterLength === 0) {
|
|
// Cleanup counter.
|
|
wipe(nc);
|
|
}
|
|
|
|
return dst;
|
|
}
|
|
|
|
/**
|
|
* Generate ChaCha20 stream for the given 32-byte key and 8-byte or 12-byte
|
|
* nonce and write it into dst and return it.
|
|
*
|
|
* Never use the same key and nonce to generate more than one stream.
|
|
*
|
|
* If nonceInplaceCounterLength is not 0, it behaves the same with respect to
|
|
* the nonce as described in the streamXOR documentation.
|
|
*
|
|
* stream is like streamXOR with all-zero src.
|
|
*/
|
|
export function stream(key: Uint8Array, nonce: Uint8Array,
|
|
dst: Uint8Array, nonceInplaceCounterLength = 0): Uint8Array {
|
|
wipe(dst);
|
|
return streamXOR(key, nonce, dst, dst, nonceInplaceCounterLength);
|
|
}
|
|
|
|
function incrementCounter(counter: Uint8Array, pos: number, len: number) {
|
|
let carry = 1;
|
|
while (len--) {
|
|
carry = carry + (counter[pos] & 0xff) | 0;
|
|
counter[pos] = carry & 0xff;
|
|
carry >>>= 8;
|
|
pos++;
|
|
}
|
|
if (carry > 0) {
|
|
throw new Error("ChaCha: counter overflow");
|
|
}
|
|
}
|