UNPKG

@stablelib/chacha

Version:
221 lines (194 loc) 8.74 kB
// 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"); } }