UNPKG

@stablelib/xchacha20

Version:
228 lines (207 loc) 7.36 kB
// Copyright (C) 2019 Kyle Den Hartog // MIT License. See LICENSE file for details. /** * Package xchacha20 implements XChaCha20 stream cipher. */ import { writeUint32LE } from "@stablelib/binary"; import { wipe } from "@stablelib/wipe"; import { streamXOR as chachaStreamXOR } from "@stablelib/chacha"; // Number of ChaCha rounds (ChaCha20). const ROUNDS = 20; /** * Encrypt src with XChaCha20 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. * * Nonce length is set in such a way that given it's generated via a CSPRNG * then there's little concern of collision for roughly 2^96 messages while * reusing a secret key and not encountering nonce reuse vulnerabilities. */ export function streamXOR(key: Uint8Array, nonce: Uint8Array, src: Uint8Array, dst: Uint8Array): Uint8Array { if (nonce.length !== 24) { throw new Error("XChaCha20 nonce must be 24 bytes"); } // Use HChaCha one-way function to transform first 16 bytes of // 24-byte extended nonce and key into a new key for Salsa // stream -- "subkey". const subkey = hchacha(key, nonce.subarray(0, 16), new Uint8Array(32)); // Use last 8 bytes of 24-byte extended nonce as an actual nonce prefixed by 4 zero bytes, // and a subkey derived in the previous step as key to encrypt. const modifiedNonce = new Uint8Array(12); modifiedNonce.set(nonce.subarray(16), 4); // If nonceInplaceCounterLength > 0, we'll still pass the correct // nonce || counter, as we don't limit the end of nonce subarray. const result = chachaStreamXOR(subkey, modifiedNonce, src, dst); // Clean subkey. wipe(subkey); return result; } /** * Generate XChaCha20 stream for the given 32-byte key and 12-byte * nonce (last 8 bytes of 24 byte nonce prefixed with 4 zero bytes) * and write it into dst and return it. * * Nonces MUST be generated using an CSPRNG to generate a sufficiently * random nonce such that a collision is highly unlikely to occur. * * stream is like streamXOR with all-zero src. */ export function stream(key: Uint8Array, nonce: Uint8Array, dst: Uint8Array): Uint8Array { wipe(dst); return streamXOR(key, nonce, dst, dst); } /** * HChaCha is a one-way function used in XChaCha to extend nonce. * * It takes 32-byte key and 16-byte src and writes 32-byte result * into dst and returns it. */ export function hchacha( key: Uint8Array, src: Uint8Array, dst: Uint8Array ): Uint8Array { 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 = (src[3] << 24) | (src[2] << 16) | (src[1] << 8) | src[0]; let j13 = (src[7] << 24) | (src[6] << 16) | (src[5] << 8) | src[4]; let j14 = (src[11] << 24) | (src[10] << 16) | (src[9] << 8) | src[8]; let j15 = (src[15] << 24) | (src[14] << 16) | (src[13] << 8) | src[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, dst, 0); writeUint32LE(x1, dst, 4); writeUint32LE(x2, dst, 8); writeUint32LE(x3, dst, 12); writeUint32LE(x12, dst, 16); writeUint32LE(x13, dst, 20); writeUint32LE(x14, dst, 24); writeUint32LE(x15, dst, 28); return dst; }