Add crypto_secretstream
methods (#57)
This commit is contained in:
parent
426ca77d47
commit
6c584231e6
271
crypto_secretstream.js
Normal file
271
crypto_secretstream.js
Normal file
@ -0,0 +1,271 @@
|
||||
/* eslint-disable camelcase */
|
||||
const assert = require('nanoassert')
|
||||
const { randombytes_buf } = require('./randombytes')
|
||||
const {
|
||||
crypto_stream_chacha20_ietf,
|
||||
crypto_stream_chacha20_ietf_xor,
|
||||
crypto_stream_chacha20_ietf_xor_ic,
|
||||
crypto_stream_chacha20_ietf_KEYBYTES
|
||||
} = require('./crypto_stream_chacha20')
|
||||
const { crypto_core_hchacha20, crypto_core_hchacha20_INPUTBYTES } = require('./internal/hchacha20')
|
||||
const Poly1305 = require('./internal/poly1305')
|
||||
const { sodium_increment, sodium_is_zero, sodium_memcmp } = require('./helpers')
|
||||
|
||||
const crypto_onetimeauth_poly1305_BYTES = 16
|
||||
const crypto_secretstream_xchacha20poly1305_COUNTERBYTES = 4
|
||||
const crypto_secretstream_xchacha20poly1305_INONCEBYTES = 8
|
||||
const crypto_aead_xchacha20poly1305_ietf_KEYBYTES = 32
|
||||
const crypto_secretstream_xchacha20poly1305_KEYBYTES = crypto_aead_xchacha20poly1305_ietf_KEYBYTES
|
||||
const crypto_aead_xchacha20poly1305_ietf_NPUBBYTES = 24
|
||||
const crypto_secretstream_xchacha20poly1305_HEADERBYTES = crypto_aead_xchacha20poly1305_ietf_NPUBBYTES
|
||||
const crypto_aead_xchacha20poly1305_ietf_ABYTES = 16
|
||||
const crypto_secretstream_xchacha20poly1305_ABYTES = 1 + crypto_aead_xchacha20poly1305_ietf_ABYTES
|
||||
const crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX = Number.MAX_SAFE_INTEGER
|
||||
const crypto_aead_chacha20poly1305_ietf_MESSAGEBYTES_MAX = Number.MAX_SAFE_INTEGER
|
||||
const crypto_secretstream_xchacha20poly1305_TAGBYTES = 1
|
||||
const crypto_secretstream_xchacha20poly1305_TAG_MESSAGE = new Uint8Array([0])
|
||||
const crypto_secretstream_xchacha20poly1305_TAG_PUSH = new Uint8Array([1])
|
||||
const crypto_secretstream_xchacha20poly1305_TAG_REKEY = new Uint8Array([2])
|
||||
const crypto_secretstream_xchacha20poly1305_TAG_FINAL = new Uint8Array([crypto_secretstream_xchacha20poly1305_TAG_PUSH | crypto_secretstream_xchacha20poly1305_TAG_REKEY])
|
||||
const crypto_secretstream_xchacha20poly1305_STATEBYTES = crypto_secretstream_xchacha20poly1305_KEYBYTES +
|
||||
crypto_secretstream_xchacha20poly1305_INONCEBYTES + crypto_secretstream_xchacha20poly1305_COUNTERBYTES + 8
|
||||
|
||||
const KEY_OFFSET = 0
|
||||
const NONCE_OFFSET = crypto_secretstream_xchacha20poly1305_KEYBYTES
|
||||
const PAD_OFFSET = NONCE_OFFSET + crypto_secretstream_xchacha20poly1305_INONCEBYTES + crypto_secretstream_xchacha20poly1305_COUNTERBYTES
|
||||
|
||||
const _pad0 = new Uint8Array(16)
|
||||
|
||||
function STORE64_LE (dest, int) {
|
||||
let mul = 1
|
||||
let i = 0
|
||||
dest[0] = int & 0xFF
|
||||
while (++i < 8 && (mul *= 0x100)) {
|
||||
dest[i] = (int / mul) & 0xFF
|
||||
}
|
||||
}
|
||||
|
||||
function crypto_secretstream_xchacha20poly1305_counter_reset (state) {
|
||||
assert(state.byteLength === crypto_secretstream_xchacha20poly1305_STATEBYTES,
|
||||
'state is should be crypto_secretstream_xchacha20poly1305_STATEBYTES long')
|
||||
|
||||
const nonce = state.subarray(NONCE_OFFSET, PAD_OFFSET)
|
||||
for (let i = 0; i < crypto_secretstream_xchacha20poly1305_COUNTERBYTES; i++) {
|
||||
nonce[i] = 0
|
||||
}
|
||||
nonce[0] = 1
|
||||
}
|
||||
|
||||
function crypto_secretstream_xchacha20poly1305_keygen (k) {
|
||||
assert(k.length === crypto_secretstream_xchacha20poly1305_KEYBYTES)
|
||||
randombytes_buf(k)
|
||||
}
|
||||
|
||||
function crypto_secretstream_xchacha20poly1305_init_push (state, out, key) {
|
||||
assert(state.byteLength === crypto_secretstream_xchacha20poly1305_STATEBYTES,
|
||||
'state is should be crypto_secretstream_xchacha20poly1305_STATEBYTES long')
|
||||
assert(out instanceof Uint8Array && out.length === crypto_secretstream_xchacha20poly1305_HEADERBYTES, 'out not byte array of length crypto_secretstream_xchacha20poly1305_HEADERBYTES')
|
||||
assert(key instanceof Uint8Array && key.length === crypto_secretstream_xchacha20poly1305_KEYBYTES, 'key not byte array of length crypto_secretstream_xchacha20poly1305_KEYBYTES')
|
||||
|
||||
const k = state.subarray(KEY_OFFSET, NONCE_OFFSET)
|
||||
const nonce = state.subarray(NONCE_OFFSET, PAD_OFFSET)
|
||||
const pad = state.subarray(PAD_OFFSET)
|
||||
|
||||
randombytes_buf(out, crypto_secretstream_xchacha20poly1305_HEADERBYTES)
|
||||
crypto_core_hchacha20(k, out, key, null)
|
||||
crypto_secretstream_xchacha20poly1305_counter_reset(state)
|
||||
for (let i = 0; i < crypto_secretstream_xchacha20poly1305_INONCEBYTES; i++) {
|
||||
nonce[i + crypto_secretstream_xchacha20poly1305_COUNTERBYTES] = out[i + crypto_core_hchacha20_INPUTBYTES]
|
||||
}
|
||||
pad.fill(0)
|
||||
}
|
||||
|
||||
function crypto_secretstream_xchacha20poly1305_init_pull (state, _in, key) {
|
||||
assert(state.byteLength === crypto_secretstream_xchacha20poly1305_STATEBYTES,
|
||||
'state is should be crypto_secretstream_xchacha20poly1305_STATEBYTES long')
|
||||
assert(_in instanceof Uint8Array && _in.length === crypto_secretstream_xchacha20poly1305_HEADERBYTES,
|
||||
'_in not byte array of length crypto_secretstream_xchacha20poly1305_HEADERBYTES')
|
||||
assert(key instanceof Uint8Array && key.length === crypto_secretstream_xchacha20poly1305_KEYBYTES,
|
||||
'key not byte array of length crypto_secretstream_xchacha20poly1305_KEYBYTES')
|
||||
|
||||
const k = state.subarray(KEY_OFFSET, NONCE_OFFSET)
|
||||
const nonce = state.subarray(NONCE_OFFSET, PAD_OFFSET)
|
||||
const pad = state.subarray(PAD_OFFSET)
|
||||
|
||||
crypto_core_hchacha20(k, _in, key, null)
|
||||
crypto_secretstream_xchacha20poly1305_counter_reset(state)
|
||||
|
||||
for (let i = 0; i < crypto_secretstream_xchacha20poly1305_INONCEBYTES; i++) {
|
||||
nonce[i + crypto_secretstream_xchacha20poly1305_COUNTERBYTES] = _in[i + crypto_core_hchacha20_INPUTBYTES]
|
||||
}
|
||||
pad.fill(0)
|
||||
}
|
||||
|
||||
function crypto_secretstream_xchacha20poly1305_rekey (state) {
|
||||
assert(state.byteLength === crypto_secretstream_xchacha20poly1305_STATEBYTES,
|
||||
'state is should be crypto_secretstream_xchacha20poly1305_STATEBYTES long')
|
||||
|
||||
const k = state.subarray(KEY_OFFSET, NONCE_OFFSET)
|
||||
const nonce = state.subarray(NONCE_OFFSET, PAD_OFFSET)
|
||||
|
||||
const new_key_and_inonce = new Uint8Array(
|
||||
crypto_stream_chacha20_ietf_KEYBYTES + crypto_secretstream_xchacha20poly1305_INONCEBYTES)
|
||||
let i
|
||||
for (i = 0; i < crypto_stream_chacha20_ietf_KEYBYTES; i++) {
|
||||
new_key_and_inonce[i] = k[i]
|
||||
}
|
||||
for (i = 0; i < crypto_secretstream_xchacha20poly1305_INONCEBYTES; i++) {
|
||||
new_key_and_inonce[crypto_stream_chacha20_ietf_KEYBYTES + i] =
|
||||
nonce[crypto_secretstream_xchacha20poly1305_COUNTERBYTES + i]
|
||||
}
|
||||
crypto_stream_chacha20_ietf_xor(new_key_and_inonce, new_key_and_inonce, nonce, k)
|
||||
for (i = 0; i < crypto_stream_chacha20_ietf_KEYBYTES; i++) {
|
||||
k[i] = new_key_and_inonce[i]
|
||||
}
|
||||
for (i = 0; i < crypto_secretstream_xchacha20poly1305_INONCEBYTES; i++) {
|
||||
nonce[crypto_secretstream_xchacha20poly1305_COUNTERBYTES + i] =
|
||||
new_key_and_inonce[crypto_stream_chacha20_ietf_KEYBYTES + i]
|
||||
}
|
||||
crypto_secretstream_xchacha20poly1305_counter_reset(state)
|
||||
}
|
||||
|
||||
function crypto_secretstream_xchacha20poly1305_push (state, out, m, ad, tag) {
|
||||
assert(state.byteLength === crypto_secretstream_xchacha20poly1305_STATEBYTES,
|
||||
'state is should be crypto_secretstream_xchacha20poly1305_STATEBYTES long')
|
||||
if (!ad) ad = new Uint8Array(0)
|
||||
|
||||
const k = state.subarray(KEY_OFFSET, NONCE_OFFSET)
|
||||
const nonce = state.subarray(NONCE_OFFSET, PAD_OFFSET)
|
||||
|
||||
const block = new Uint8Array(64)
|
||||
const slen = new Uint8Array(8)
|
||||
|
||||
assert(crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX <=
|
||||
crypto_aead_chacha20poly1305_ietf_MESSAGEBYTES_MAX)
|
||||
|
||||
crypto_stream_chacha20_ietf(block, nonce, k)
|
||||
const poly = new Poly1305(block)
|
||||
block.fill(0)
|
||||
|
||||
poly.update(ad, 0, ad.byteLength)
|
||||
poly.update(_pad0, 0, (0x10 - ad.byteLength) & 0xf)
|
||||
|
||||
block[0] = tag[0]
|
||||
crypto_stream_chacha20_ietf_xor_ic(block, block, nonce, 1, k)
|
||||
|
||||
poly.update(block, 0, block.byteLength)
|
||||
out[0] = block[0]
|
||||
|
||||
const c = out.subarray(1, out.byteLength)
|
||||
crypto_stream_chacha20_ietf_xor_ic(c, m, nonce, 2, k)
|
||||
poly.update(c, 0, m.byteLength)
|
||||
poly.update(_pad0, 0, (0x10 - block.byteLength + m.byteLength) & 0xf)
|
||||
|
||||
STORE64_LE(slen, ad.byteLength)
|
||||
poly.update(slen, 0, slen.byteLength)
|
||||
STORE64_LE(slen, block.byteLength + m.byteLength)
|
||||
poly.update(slen, 0, slen.byteLength)
|
||||
|
||||
const mac = out.subarray(1 + m.byteLength, out.byteLength)
|
||||
poly.finish(mac, 0)
|
||||
|
||||
assert(crypto_onetimeauth_poly1305_BYTES >=
|
||||
crypto_secretstream_xchacha20poly1305_INONCEBYTES)
|
||||
xor_buf(nonce.subarray(crypto_secretstream_xchacha20poly1305_COUNTERBYTES, nonce.length),
|
||||
mac, crypto_secretstream_xchacha20poly1305_INONCEBYTES)
|
||||
sodium_increment(nonce)
|
||||
|
||||
if ((tag[0] & crypto_secretstream_xchacha20poly1305_TAG_REKEY) !== 0 ||
|
||||
sodium_is_zero(nonce.subarray(0, crypto_secretstream_xchacha20poly1305_COUNTERBYTES))) {
|
||||
crypto_secretstream_xchacha20poly1305_rekey(state)
|
||||
}
|
||||
|
||||
return crypto_secretstream_xchacha20poly1305_ABYTES + m.byteLength
|
||||
}
|
||||
|
||||
function crypto_secretstream_xchacha20poly1305_pull (state, m, tag, _in, ad) {
|
||||
assert(state.byteLength === crypto_secretstream_xchacha20poly1305_STATEBYTES,
|
||||
'state is should be crypto_secretstream_xchacha20poly1305_STATEBYTES long')
|
||||
if (!ad) ad = new Uint8Array(0)
|
||||
|
||||
const k = state.subarray(KEY_OFFSET, NONCE_OFFSET)
|
||||
const nonce = state.subarray(NONCE_OFFSET, PAD_OFFSET)
|
||||
|
||||
const block = new Uint8Array(64)
|
||||
const slen = new Uint8Array(8)
|
||||
const mac = new Uint8Array(crypto_onetimeauth_poly1305_BYTES)
|
||||
|
||||
assert(_in.byteLength >= crypto_secretstream_xchacha20poly1305_ABYTES,
|
||||
'ciphertext is too short.')
|
||||
|
||||
const mlen = _in.byteLength - crypto_secretstream_xchacha20poly1305_ABYTES
|
||||
crypto_stream_chacha20_ietf(block, nonce, k)
|
||||
const poly = new Poly1305(block)
|
||||
block.fill(0) // sodium_memzero(block, sizeof block);
|
||||
|
||||
poly.update(ad, 0, ad.byteLength)
|
||||
poly.update(_pad0, 0, (0x10 - ad.byteLength) & 0xf)
|
||||
|
||||
block.fill(0) // memset(block, 0, sizeof block);
|
||||
block[0] = _in[0]
|
||||
crypto_stream_chacha20_ietf_xor_ic(block, block, nonce, 1, k)
|
||||
|
||||
tag[0] = block[0]
|
||||
block[0] = _in[0]
|
||||
poly.update(block, 0, block.byteLength)
|
||||
|
||||
const c = _in.subarray(1, _in.length)
|
||||
poly.update(c, 0, mlen)
|
||||
|
||||
poly.update(_pad0, 0, (0x10 - block.byteLength + mlen) & 0xf)
|
||||
|
||||
STORE64_LE(slen, ad.byteLength)
|
||||
poly.update(slen, 0, slen.byteLength)
|
||||
STORE64_LE(slen, block.byteLength + m.byteLength)
|
||||
poly.update(slen, 0, slen.byteLength)
|
||||
|
||||
poly.finish(mac, 0)
|
||||
const stored_mac = _in.subarray(1 + mlen, _in.length)
|
||||
|
||||
if (!sodium_memcmp(mac, stored_mac)) {
|
||||
mac.fill(0)
|
||||
throw new Error('MAC could not be verified.')
|
||||
}
|
||||
|
||||
crypto_stream_chacha20_ietf_xor_ic(m, c.subarray(0, m.length), nonce, 2, k)
|
||||
xor_buf(nonce.subarray(crypto_secretstream_xchacha20poly1305_COUNTERBYTES, nonce.length),
|
||||
mac, crypto_secretstream_xchacha20poly1305_INONCEBYTES)
|
||||
sodium_increment(nonce)
|
||||
|
||||
if ((tag & crypto_secretstream_xchacha20poly1305_TAG_REKEY) !== 0 ||
|
||||
sodium_is_zero(nonce.subarray(0, crypto_secretstream_xchacha20poly1305_COUNTERBYTES))) {
|
||||
crypto_secretstream_xchacha20poly1305_rekey(state)
|
||||
}
|
||||
|
||||
return mlen
|
||||
}
|
||||
|
||||
function xor_buf (out, _in, n) {
|
||||
for (let i = 0; i < n; i++) {
|
||||
out[i] ^= _in[i]
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
crypto_secretstream_xchacha20poly1305_keygen,
|
||||
crypto_secretstream_xchacha20poly1305_init_push,
|
||||
crypto_secretstream_xchacha20poly1305_init_pull,
|
||||
crypto_secretstream_xchacha20poly1305_rekey,
|
||||
crypto_secretstream_xchacha20poly1305_push,
|
||||
crypto_secretstream_xchacha20poly1305_pull,
|
||||
crypto_secretstream_xchacha20poly1305_STATEBYTES,
|
||||
crypto_secretstream_xchacha20poly1305_ABYTES,
|
||||
crypto_secretstream_xchacha20poly1305_HEADERBYTES,
|
||||
crypto_secretstream_xchacha20poly1305_KEYBYTES,
|
||||
crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX,
|
||||
crypto_secretstream_xchacha20poly1305_TAGBYTES,
|
||||
crypto_secretstream_xchacha20poly1305_TAG_MESSAGE,
|
||||
crypto_secretstream_xchacha20poly1305_TAG_PUSH,
|
||||
crypto_secretstream_xchacha20poly1305_TAG_REKEY,
|
||||
crypto_secretstream_xchacha20poly1305_TAG_FINAL
|
||||
}
|
1
index.js
1
index.js
@ -23,6 +23,7 @@ forward(require('./crypto_aead'))
|
||||
forward(require('./crypto_onetimeauth'))
|
||||
forward(require('./crypto_scalarmult'))
|
||||
forward(require('./crypto_secretbox'))
|
||||
forward(require('./crypto_secretstream'))
|
||||
forward(require('./crypto_shorthash'))
|
||||
forward(require('./crypto_sign'))
|
||||
forward(require('./crypto_stream'))
|
||||
|
128
internal/hchacha20.js
Normal file
128
internal/hchacha20.js
Normal file
@ -0,0 +1,128 @@
|
||||
/* eslint-disable camelcase */
|
||||
const { sodium_malloc } = require('../memory')
|
||||
const assert = require('nanoassert')
|
||||
|
||||
if (new Uint16Array([1])[0] !== 1) throw new Error('Big endian architecture is not supported.')
|
||||
|
||||
const crypto_core_hchacha20_OUTPUTBYTES = 32
|
||||
const crypto_core_hchacha20_INPUTBYTES = 16
|
||||
const crypto_core_hchacha20_KEYBYTES = 32
|
||||
const crypto_core_hchacha20_CONSTBYTES = 16
|
||||
|
||||
function ROTL32 (x, b) {
|
||||
x &= 0xFFFFFFFF
|
||||
b &= 0xFFFFFFFF
|
||||
return (x << b) | (x >>> (32 - b))
|
||||
}
|
||||
|
||||
function LOAD32_LE (src, offset) {
|
||||
assert(src instanceof Uint8Array, 'src not byte array')
|
||||
let w = src[offset]
|
||||
w |= src[offset + 1] << 8
|
||||
w |= src[offset + 2] << 16
|
||||
w |= src[offset + 3] << 24
|
||||
return w
|
||||
}
|
||||
|
||||
function STORE32_LE (dest, int, offset) {
|
||||
assert(dest instanceof Uint8Array, 'dest not byte array')
|
||||
var mul = 1
|
||||
var i = 0
|
||||
dest[offset] = int & 0xFF // grab bottom byte
|
||||
while (++i < 4 && (mul *= 0x100)) {
|
||||
dest[offset + i] = (int / mul) & 0xFF
|
||||
}
|
||||
}
|
||||
|
||||
function QUARTERROUND (l, A, B, C, D) {
|
||||
l[A] += l[B]
|
||||
l[D] = ROTL32(l[D] ^ l[A], 16)
|
||||
l[C] += l[D]
|
||||
l[B] = ROTL32(l[B] ^ l[C], 12)
|
||||
l[A] += l[B]
|
||||
l[D] = ROTL32(l[D] ^ l[A], 8)
|
||||
l[C] += l[D]
|
||||
l[B] = ROTL32(l[B] ^ l[C], 7)
|
||||
}
|
||||
|
||||
function crypto_core_hchacha20 (out, _in, k, c) {
|
||||
assert(out instanceof Uint8Array && out.length === 32, 'out is not an array of 32 bytes')
|
||||
assert(k instanceof Uint8Array && k.length === 32, 'k is not an array of 32 bytes')
|
||||
assert(c === null || (c instanceof Uint8Array && c.length === 16), 'c is not null or an array of 16 bytes')
|
||||
|
||||
let i = 0
|
||||
const x = new Uint32Array(16)
|
||||
if (!c) {
|
||||
x[0] = 0x61707865
|
||||
x[1] = 0x3320646E
|
||||
x[2] = 0x79622D32
|
||||
x[3] = 0x6B206574
|
||||
} else {
|
||||
x[0] = LOAD32_LE(c, 0)
|
||||
x[1] = LOAD32_LE(c, 4)
|
||||
x[2] = LOAD32_LE(c, 8)
|
||||
x[3] = LOAD32_LE(c, 12)
|
||||
}
|
||||
x[4] = LOAD32_LE(k, 0)
|
||||
x[5] = LOAD32_LE(k, 4)
|
||||
x[6] = LOAD32_LE(k, 8)
|
||||
x[7] = LOAD32_LE(k, 12)
|
||||
x[8] = LOAD32_LE(k, 16)
|
||||
x[9] = LOAD32_LE(k, 20)
|
||||
x[10] = LOAD32_LE(k, 24)
|
||||
x[11] = LOAD32_LE(k, 28)
|
||||
x[12] = LOAD32_LE(_in, 0)
|
||||
x[13] = LOAD32_LE(_in, 4)
|
||||
x[14] = LOAD32_LE(_in, 8)
|
||||
x[15] = LOAD32_LE(_in, 12)
|
||||
|
||||
for (i = 0; i < 10; i++) {
|
||||
QUARTERROUND(x, 0, 4, 8, 12)
|
||||
QUARTERROUND(x, 1, 5, 9, 13)
|
||||
QUARTERROUND(x, 2, 6, 10, 14)
|
||||
QUARTERROUND(x, 3, 7, 11, 15)
|
||||
QUARTERROUND(x, 0, 5, 10, 15)
|
||||
QUARTERROUND(x, 1, 6, 11, 12)
|
||||
QUARTERROUND(x, 2, 7, 8, 13)
|
||||
QUARTERROUND(x, 3, 4, 9, 14)
|
||||
}
|
||||
|
||||
STORE32_LE(out, x[0], 0)
|
||||
STORE32_LE(out, x[1], 4)
|
||||
STORE32_LE(out, x[2], 8)
|
||||
STORE32_LE(out, x[3], 12)
|
||||
STORE32_LE(out, x[12], 16)
|
||||
STORE32_LE(out, x[13], 20)
|
||||
STORE32_LE(out, x[14], 24)
|
||||
STORE32_LE(out, x[15], 28)
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
function crypto_core_hchacha20_outputbytes () {
|
||||
return crypto_core_hchacha20_OUTPUTBYTES
|
||||
}
|
||||
|
||||
function crypto_core_hchacha20_inputbytes () {
|
||||
return crypto_core_hchacha20_INPUTBYTES
|
||||
}
|
||||
|
||||
function crypto_core_hchacha20_keybytes () {
|
||||
return crypto_core_hchacha20_KEYBYTES
|
||||
}
|
||||
|
||||
function crypto_core_hchacha20_constbytes () {
|
||||
return crypto_core_hchacha20_CONSTBYTES
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
crypto_core_hchacha20_INPUTBYTES,
|
||||
LOAD32_LE,
|
||||
STORE32_LE,
|
||||
QUARTERROUND,
|
||||
crypto_core_hchacha20,
|
||||
crypto_core_hchacha20_outputbytes,
|
||||
crypto_core_hchacha20_inputbytes,
|
||||
crypto_core_hchacha20_keybytes,
|
||||
crypto_core_hchacha20_constbytes
|
||||
}
|
Loading…
Reference in New Issue
Block a user