@stablelib/chacha
Version:
ChaCha stream cipher
221 lines (194 loc) • 8.74 kB
text/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");
}
}