Skip to content

Commit

Permalink
feat(box.js): Introduce box encryption
Browse files Browse the repository at this point in the history
  • Loading branch information
jwerle committed Jul 18, 2018
1 parent 979e8d9 commit f4dae43
Showing 1 changed file with 169 additions and 0 deletions.
169 changes: 169 additions & 0 deletions box.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
const increment = require('increment-buffer')
const isBuffer = require('is-buffer')
const through = require('through2')
const split = require('split-buffer')

/* eslint-disable camelcase */
const {
crypto_secretbox_KEYBYTES,
crypto_secretbox_MACBYTES,

crypto_secretbox_easy,
} = require('sodium-universal')

const kBoxBufferSize = 4 * 1024

/**
* Encrypts a buffer with a given key and an optional nonce.
*
* @public
* @param {Buffer} buffer
* @param {Object} opts
* @param {Buffer} opts.buffer
* @param {?(Buffer)} opts.secret
* @param {?(Buffer)} opts.nonce
* @param {?(Buffer)} opts.key
* @return {Buffer}
* @throws TypeError
*/
function box(buffer, opts) {
if (!opts || 'object' !== typeof opts) {
throw new TypeError('crypto.box: Expecting object.')
}

const { secret } = opts
let { nonce, key } = opts

if (!buffer || false === isBuffer(buffer)) {
throw new TypeError('crypto.box: Expecting buffer.')
}

if (isBuffer(secret) && secret.length >= crypto_secretbox_KEYBYTES) {
key = secret.slice(0, crypto_secretbox_KEYBYTES)
nonce = isBuffer(opts.nonce)
? opts.nonce
: secret.slice(crypto_secretbox_KEYBYTES)
}

if (false === isBuffer(nonce)) {
throw new TypeError('crypto.box: Expecting nonce.')
}

if (false === isBuffer(key)) {
throw new TypeError('crypto.box: Expecting secret key')
}

// ephemeral nonces used for header and body buffer boxing
const nonces = [ copy(nonce), increment(copy(nonce)) ]

// length(2) + MAC(16) == 18
const header = Buffer.alloc(2 + crypto_secretbox_MACBYTES)

// boxed buffer cipher text
const body = Buffer.alloc(crypto_secretbox_MACBYTES + buffer.length)

// output buffer frames
const frames = [
Buffer.alloc(crypto_secretbox_MACBYTES + header.length),
Buffer.alloc(buffer.length),
]

// write buffer length into header
header.writeUInt16BE(buffer.length, 0)

// box message buffer
crypto_secretbox_easy(body, buffer, nonces[1], key)

// copy MAC into header after length (offset 2)
body.copy(
header,
2,
0,
crypto_secretbox_MACBYTES
)

// copy boxed message buffer into frames[1]
body.copy(
frames[1],
0,
crypto_secretbox_MACBYTES,
crypto_secretbox_MACBYTES + buffer.length
)

// box header buffer into frame[0] based on current nonces[0] and key
crypto_secretbox_easy(frames[0], header, nonces[0], key)

return Object.assign(Buffer.concat(frames), { nonce: nonces[1] })
}

/**
* Creates a transform stream that "boxes" messages
* written to it.
*
* @public
* @param {Object} opts
* @param {?(Buffer)} opts.secret
* @param {?(Buffer)} opts.nonce
* @param {?(Buffer)} opts.key
* @return {Stream}
* @throws TypeError
*/
function createBoxStream(opts) {
if (!opts || 'object' !== typeof opts) {
throw new TypeError('crypto.box: Expecting object.')
}

// create new reference
/* eslint-disable-next-line no-param-reassign */
opts = Object.assign({}, opts)

if (false === isBuffer(opts.nonce)) {
throw new TypeError('crypto.createBoxStream: Expecting nonce')
}

const final = isBuffer(opts.final)
? opts.final
: zeroes(2 + crypto_secretbox_MACBYTES)

return through(transform, flush)

function transform(buffer, enc, done) {
const chunks = split(buffer, kBoxBufferSize)
for (const chunk of chunks) {
const offset = 2 + (2 * crypto_secretbox_MACBYTES)
const boxed = box(chunk, opts)
const nonce = increment(copy(boxed.nonce))
const head = boxed.slice(0, offset)
const body = boxed.slice(offset)

this.push(head)
this.push(body)

Object.assign(opts, { nonce })
}

done(null)
}

function flush(done) {
const end = box(final, opts)
done(null, end)
}
}

function zeroes(n) {
const z = Buffer.allocUnsafe(n)
z.fill(0)
return z
}

function copy(x) {
const y = Buffer.allocUnsafe(x.length)
x.copy(y, 0, 0, x.length)
return y
}

module.exports = {
createBoxStream,
box,
}

0 comments on commit f4dae43

Please sign in to comment.