diff --git a/crypto_secretbox.js b/crypto_secretbox.js index d33b8f5..a5aa6f3 100644 --- a/crypto_secretbox.js +++ b/crypto_secretbox.js @@ -21,24 +21,24 @@ module.exports = { crypto_secretbox_MACBYTES } -function crypto_secretbox(c,m,d,n,k) { - var i; - if (d < 32) return -1; - crypto_stream_xor(c,0,m,0,d,n,k); - crypto_onetimeauth(c, 16, c, 32, d - 32, c); - for (i = 0; i < 16; i++) c[i] = 0; - return 0; +function crypto_secretbox (c, m, d, n, k) { + var i + if (d < 32) return -1 + crypto_stream_xor(c, m, n, k) + crypto_onetimeauth(c, 16, c, 32, d - 32, c) + for (i = 0; i < 16; i++) c[i] = 0 + return 0 } -function crypto_secretbox_open(m,c,d,n,k) { - var i; - var x = new Uint8Array(32); - if (d < 32) return -1; - crypto_stream(x,0,32,n,k); - if (crypto_onetimeauth_verify(c, 16,c, 32,d - 32,x) !== 0) return -1; - crypto_stream_xor(m,0,c,0,d,n,k); - for (i = 0; i < 32; i++) m[i] = 0; - return 0; +function crypto_secretbox_open (m, c, d, n, k) { + var i + var x = new Uint8Array(32) + if (d < 32) return -1 + crypto_stream(x, n, k) + if (crypto_onetimeauth_verify(c, 16, c, 32, d - 32, x) !== 0) return -1 + crypto_stream_xor(m, c, n, k) + for (i = 0; i < 32; i++) m[i] = 0 + return 0 } function crypto_secretbox_detached (o, mac, msg, n, k) { @@ -64,10 +64,10 @@ function crypto_secretbox_easy(o, msg, n, k) { check(k, crypto_secretbox_KEYBYTES) var i - var m = new Uint8Array(crypto_secretbox_ZEROBYTES + msg.length); - var c = new Uint8Array(m.length); - for (i = 0; i < msg.length; i++) m[i+crypto_secretbox_ZEROBYTES] = msg[i]; - crypto_secretbox(c, m, m.length, n, k); + var m = new Uint8Array(crypto_secretbox_ZEROBYTES + msg.length) + var c = new Uint8Array(m.length) + for (i = 0; i < msg.length; i++) m[i+crypto_secretbox_ZEROBYTES] = msg[i] + crypto_secretbox(c, m, m.length, n, k) for (i = crypto_secretbox_BOXZEROBYTES; i < c.length; i++) o[i - crypto_secretbox_BOXZEROBYTES] = c[i] } @@ -78,11 +78,11 @@ function crypto_secretbox_open_easy(msg, box, n, k) { check(k, crypto_secretbox_KEYBYTES) var i - var c = new Uint8Array(crypto_secretbox_BOXZEROBYTES + box.length); + var c = new Uint8Array(crypto_secretbox_BOXZEROBYTES + box.length) var m = new Uint8Array(c.length); - for (i = 0; i < box.length; i++) c[i+crypto_secretbox_BOXZEROBYTES] = box[i]; - if (c.length < 32) return false; - if (crypto_secretbox_open(m, c, c.length, n, k) !== 0) return false; + for (i = 0; i < box.length; i++) c[i+crypto_secretbox_BOXZEROBYTES] = box[i] + if (c.length < 32) return false + if (crypto_secretbox_open(m, c, c.length, n, k) !== 0) return false for (i = crypto_secretbox_ZEROBYTES; i < m.length; i++) msg[i - crypto_secretbox_ZEROBYTES] = m[i] return true diff --git a/crypto_stream.js b/crypto_stream.js index a130dab..303e40d 100644 --- a/crypto_stream.js +++ b/crypto_stream.js @@ -4,12 +4,12 @@ exports.crypto_stream_KEYBYTES = 32 exports.crypto_stream_NONCEBYTES = 24 exports.crypto_stream_PRIMITIVE = 'xsalsa20' -exports.crypto_stream = function (c, cpos, clen, nonce, key) { +exports.crypto_stream = function (c, nonce, key) { c.fill(0) - exports.crypto_stream_xor(c, 0, c, 0, 0, nonce, key) + exports.crypto_stream_xor(c, c, nonce, key) } -exports.crypto_stream_xor = function (c, cpos, m, mpos, clen, nonce, key) { +exports.crypto_stream_xor = function (c, m, nonce, key) { var xor = xsalsa20(nonce, key) xor.update(m, c) diff --git a/crypto_stream_chacha20.js b/crypto_stream_chacha20.js new file mode 100644 index 0000000..f098a67 --- /dev/null +++ b/crypto_stream_chacha20.js @@ -0,0 +1,209 @@ +const assert = require('nanoassert') + +const constant = [1634760805, 857760878, 2036477234, 1797285236] + +exports.crypto_stream_chacha20_KEYBYTES = 32 +exports.crypto_stream_chacha20_NONCEBYTES = 8 +exports.crypto_stream_chacha20_MESSAGEBYTES_MAX = Number.MAX_SAFE_INTEGER + +exports.crypto_stream_chacha20_ietf_KEYBYTES = 32 +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') + + const xor = new Chacha20(n, k) + 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') + + const xor = new Chacha20(n, k, ic) + xor.update(c, m) + xor.final() +} + +exports.crypto_stream_chacha20_xor_instance = function (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(n, k) +} + +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') + + const xor = new Chacha20(n, k) + 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') + + const xor = new Chacha20(n, k, ic) + xor.update(c, m) + xor.final() +} + +exports.crypto_stream_chacha20_ietf_xor_instance = function (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(n, k) +} + +function Chacha20 (n, k, 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.pos = 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 && 0xffffffff00000000) >> 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.') + + let len = input.length + let offset = this.pos % 64 + this.pos += len + + // input position + let j = 0 + + let keyStream = chacha20Block(this.state) + + // try to finsih the current block + while (offset > 0 && len > 0) { + output[j] = input[j++] ^ keyStream[offset] + offset = (offset + 1) & 0x3f + if (!offset) this.state[12]++ + len-- + } + + // encrypt rest block at a time + while (len > 0) { + keyStream = chacha20Block(this.state) + + // less than a full block remaining + if (len < 64) { + for (let i = 0; i < len; i++) { + output[j] = input[j++] ^ keyStream[offset++] + offset &= 0x3f + } + + return + } + + for (; offset < 64;) { + output[j] = input[j++] ^ keyStream[offset++] + } + + this.state[12]++ + offset = 0 + len -= 64 + } +} + +Chacha20.prototype.final = function () { + this.finalized = true +} + +function chacha20Block (state) { + // working state + const ws = new Uint32Array(16) + for (let i = 16; i--;) ws[i] = state[i] + + for (let i = 0; i < 20; i += 2) { + QR(ws, 0, 4, 8, 12) // column 0 + QR(ws, 1, 5, 9, 13) // column 1 + QR(ws, 2, 6, 10, 14) // column 2 + QR(ws, 3, 7, 11, 15) // column 3 + + QR(ws, 0, 5, 10, 15) // diagonal 1 (main diagonal) + QR(ws, 1, 6, 11, 12) // diagonal 2 + QR(ws, 2, 7, 8, 13) // diagonal 3 + QR(ws, 3, 4, 9, 14) // diagonal 4 + } + + for (let i = 0; i < 16; i++) { + ws[i] += state[i] + } + + return Buffer.from(ws.buffer, ws.byteOffset, ws.byteLength) +} + +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) +} diff --git a/index.js b/index.js index d647646..7c4e616 100644 --- a/index.js +++ b/index.js @@ -35,6 +35,7 @@ forward(require('./crypto_secretbox')) forward(require('./crypto_shorthash')) forward(require('./crypto_sign')) forward(require('./crypto_stream')) +forward(require('./crypto_stream_chacha20')) forward(require('./randombytes')) function forward (submodule) {