From 5afea2a9ab7277004916aa5e14ab8a1a1d4c0918 Mon Sep 17 00:00:00 2001 From: Christophe Diederichs Date: Fri, 1 May 2020 19:00:03 +0200 Subject: [PATCH] method: chacha20[_ietf] added --- crypto_stream_chacha20.js | 216 ++++++++++++++++++++++++++++++++++++++ index.js | 1 + 2 files changed, 217 insertions(+) create mode 100644 crypto_stream_chacha20.js diff --git a/crypto_stream_chacha20.js b/crypto_stream_chacha20.js new file mode 100644 index 0000000..6ee5689 --- /dev/null +++ b/crypto_stream_chacha20.js @@ -0,0 +1,216 @@ +const assert = require('nanoassert') + +const MOD = 0xffffffff +const constant = [1634760805, 857760878, 2036477234, 1797285236] + +exports.crypto_stream_chacha20_KEYBYTES = 32 +exports.crypto_stream_chacha20_ietf_KEYBYTES = 32 +exports.crypto_stream_chacha20_NONCEBYTES = 8 +exports.crypto_stream_chacha20_ietf_NONCEBYTES = 12 +exports.crypto_stream_chacha20_ietf_MESSAGEBYTES_MAX = 2 ** 32 + +exports.crypto_stream_chacha20 = function (c, n, k) { + c.fill(0) + exports.crypto_stream_chacha20_xor(c, c, n, k) +} + +exports.crypto_stream_chacha20_xor = function (c, m, n, k) { + assert(n.byteLength === exports.crypto_stream_chacha20_NONCEBYTES, + 'n should be crypto_stream_chacha20_NONCEBYTES') + assert(k.byteLength === exports.crypto_stream_chacha20_KEYBYTES, + 'k should be crypto_stream_chacha20_KEYBYTES') + + var xor = new Chacha20(k, n) + xor.update(c, m) + xor.final() +} + +exports.crypto_stream_chacha20_xor_ic = function (c, m, n, ic, k) { + assert(n.byteLength === exports.crypto_stream_chacha20_NONCEBYTES, + 'n should be crypto_stream_chacha20_NONCEBYTES') + assert(k.byteLength === exports.crypto_stream_chacha20_KEYBYTES, + 'k should be crypto_stream_chacha20_KEYBYTES') + + var xor = new Chacha20(k, n, ic) + xor.update(c, m) + xor.final() +} + +exports.crypto_stream_chacha20_xor_instance = function (c, m, n, k) { + assert(n.byteLength === exports.crypto_stream_chacha20_NONCEBYTES, + 'n should be crypto_stream_chacha20_NONCEBYTES') + assert(k.byteLength === exports.crypto_stream_chacha20_KEYBYTES, + 'k should be crypto_stream_chacha20_KEYBYTES') + + return new Chacha20(k, n) +} + +exports.crypto_stream_chacha20_ietf = function (c, n, k) { + c.fill(0) + exports.crypto_stream_chacha20_ietf_xor(c, c, n, k) +} + +exports.crypto_stream_chacha20_ietf_xor = function (c, m, n, k) { + assert(n.byteLength === exports.crypto_stream_chacha20_ietf_NONCEBYTES, + 'n should be crypto_stream_chacha20_ietf_NONCEBYTES') + assert(k.byteLength === exports.crypto_stream_chacha20_ietf_KEYBYTES, + 'k should be crypto_stream_chacha20_ietf_KEYBYTES') + + var xor = new Chacha20(k, n) + xor.update(c, m) + xor.final() +} + +exports.crypto_stream_chacha20_ietf_xor_ic = function (c, m, n, ic, k) { + assert(n.byteLength === exports.crypto_stream_chacha20_ietf_NONCEBYTES, + 'n should be crypto_stream_chacha20_ietf_NONCEBYTES') + assert(k.byteLength === exports.crypto_stream_chacha20_ietf_KEYBYTES, + 'k should be crypto_stream_chacha20_ietf_KEYBYTES') + + var xor = new Chacha20(k, n, ic) + xor.update(c, m) + xor.final() +} + +exports.crypto_stream_chacha20_ietf_xor_instance = function (c, m, n, k) { + assert(n.byteLength === exports.crypto_stream_chacha20_ietf_NONCEBYTES, + 'n should be crypto_stream_chacha20_ietf_NONCEBYTES') + assert(k.byteLength === exports.crypto_stream_chacha20_ietf_KEYBYTES, + 'k should be crypto_stream_chacha20_ietf_KEYBYTES') + + return new Chacha20(k, n) +} + +function Chacha20 (k, n, counter) { + assert(k.byteLength === exports.crypto_stream_chacha20_ietf_KEYBYTES) + assert(n.byteLength === exports.crypto_stream_chacha20_NONCEBYTES || + n.byteLength === exports.crypto_stream_chacha20_ietf_NONCEBYTES) + + if (!counter) counter = 0 + assert(counter < Number.MAX_SAFE_INTEGER) + + this.finalized = false + this.offset = 0 + this.state = new Uint32Array(16) + + for (let i = 0; i < 4; i++) this.state[i] = constant[i] + for (let i = 0; i < 8; i++) this.state[4 + i] = k.readUInt32LE(4 * i) + + this.state[12] = counter & 0xffffffff + + if (n.byteLength === 8) { + this.state[13] = counter >> 32 + this.state[14] = n.readUInt32LE(0) + this.state[15] = n.readUInt32LE(4) + } else { + this.state[13] = n.readUInt32LE(0) + this.state[14] = n.readUInt32LE(4) + this.state[15] = n.readUInt32LE(8) + } + + return this +} + +Chacha20.prototype.update = function (output, input) { + assert(!this.finalized, 'cipher finalized.') + assert(output.byteLength >= input.byteLength, 'output cannot be shorter than input.') + + var len = this.offset + input.length + var offset = this.offset + var block = 0 + + while (len > 0) { + var keyStream = chacha20_block(this.state) + + if (len < 64) { + for (; offset < input.length % 64;) { + var i = block * 64 + offset + output[i] = input[i] ^ keyStream[offset++] + } + + this.offset = offset + return + } + + for (; offset < 64;) { + var i = 64 * block + offset + output[i] = input[i] ^ keyStream[offset++] + } + + offset = 0 + this.state[12]++ + len -= 64 + block++ + } +} + +Chacha20.prototype.final = function () { + this.finalized = true +} + +module.exports.keystream = function (output, key, nonce, counter) { + var c = new Chacha20(key, nonce, counter) + c.update(output, Buffer.alloc(output.length)) + c.final() +} + +function chacha20_block (state) { + var workingState = new Uint32Array(16) + for (let i = 16; i--;) workingState[i] = state[i] + + for (let i = 0; i < 20; i += 2) { + QR(workingState, 0, 4, 8, 12) // column 0 + QR(workingState, 1, 5, 9, 13) // column 1 + QR(workingState, 2, 6, 10, 14) // column 2 + QR(workingState, 3, 7, 11, 15) // column 3 + + QR(workingState, 0, 5, 10, 15) // diagonal 1 (main diagonal) + QR(workingState, 1, 6, 11, 12) // diagonal 2 + QR(workingState, 2, 7, 8, 13) // diagonal 3 + QR(workingState, 3, 4, 9, 14) // diagonal 4 + } + + for (let i = 0; i < 16; i++) { + workingState[i] += state[i] + } + + return Buffer.from(workingState.buffer) +} + +function rotl (a, b) { + return ((a << b) | (a >>> (32 - b))) +} + +function QR (obj, a, b, c, d) { + obj[a] += obj[b] + obj[d] ^= obj[a] + obj[d] = rotl(obj[d], 16) + + obj[c] += obj[d] + obj[b] ^= obj[c] + obj[b] = rotl(obj[b], 12) + + obj[a] += obj[b] + obj[d] ^= obj[a] + obj[d] = rotl(obj[d], 8) + + obj[c] += obj[d] + obj[b] ^= obj[c] + obj[b] = rotl(obj[b], 7) +} + +function arrToBuffer (buf, arr) { + for (let i = 0; i < arr.length; i++) { + buf.writeUInt32LE(arr[i], 4 * i) + } + + return buf +} + +function bufferToArr (arr, buf) { + for (let i = 0; i < arr.length; i++) { + arr[i] = buf.readUInt32LE(4 * i) + } + + return arr +} diff --git a/index.js b/index.js index c7b26de..f50c9e9 100644 --- a/index.js +++ b/index.js @@ -1829,6 +1829,7 @@ forward(require('./crypto_kdf')) forward(require('./crypto_shorthash')) forward(require('./randombytes')) forward(require('./crypto_stream')) +forward(require('./crypto_stream_chacha20')) sodium.crypto_scalarmult_BYTES = crypto_scalarmult_BYTES sodium.crypto_scalarmult_SCALARBYTES = crypto_scalarmult_SCALARBYTES