fcoin/lib/crypto/aead.js
2017-08-06 21:36:48 -07:00

173 lines
3.4 KiB
JavaScript

/*!
* aead.js - aead for bcoin
* Copyright (c) 2016-2017, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
const assert = require('assert');
const ChaCha20 = require('./chacha20');
const Poly1305 = require('./poly1305');
/**
* AEAD (used for bip151)
* @alias module:crypto.AEAD
* @constructor
* @see https://github.com/openssh/openssh-portable
* @see https://tools.ietf.org/html/rfc7539#section-2.8
*/
function AEAD() {
if (!(this instanceof AEAD))
return new AEAD();
this.chacha20 = new ChaCha20();
this.poly1305 = new Poly1305();
this.aadLen = 0;
this.cipherLen = 0;
this.polyKey = null;
}
/**
* Initialize the AEAD with a key and iv.
* @param {Buffer} key
* @param {Buffer} iv - IV / packet sequence number.
*/
AEAD.prototype.init = function init(key, iv) {
const polyKey = Buffer.allocUnsafe(32);
polyKey.fill(0);
this.chacha20.init(key, iv);
this.chacha20.encrypt(polyKey);
this.poly1305.init(polyKey);
// We need to encrypt a full block
// to get the cipher in the correct state.
this.chacha20.encrypt(Buffer.allocUnsafe(32));
// Counter should be one.
assert(this.chacha20.getCounter() === 1);
// Expose for debugging.
this.polyKey = polyKey;
this.aadLen = 0;
this.cipherLen = 0;
};
/**
* Update the aad (will be finalized
* on an encrypt/decrypt call).
* @param {Buffer} aad
*/
AEAD.prototype.aad = function aad(data) {
assert(this.cipherLen === 0, 'Cannot update aad.');
this.poly1305.update(data);
this.aadLen += data.length;
};
/**
* Encrypt a piece of data.
* @param {Buffer} data
*/
AEAD.prototype.encrypt = function encrypt(data) {
if (this.cipherLen === 0)
this.pad16(this.aadLen);
this.chacha20.encrypt(data);
this.poly1305.update(data);
this.cipherLen += data.length;
return data;
};
/**
* Decrypt a piece of data.
* @param {Buffer} data
*/
AEAD.prototype.decrypt = function decrypt(data) {
if (this.cipherLen === 0)
this.pad16(this.aadLen);
this.cipherLen += data.length;
this.poly1305.update(data);
this.chacha20.encrypt(data);
return data;
};
/**
* Authenticate data without decrypting.
* @param {Buffer} data
*/
AEAD.prototype.auth = function auth(data) {
if (this.cipherLen === 0)
this.pad16(this.aadLen);
this.cipherLen += data.length;
this.poly1305.update(data);
return data;
};
/**
* Finalize the aead and generate a MAC.
* @returns {Buffer} MAC
*/
AEAD.prototype.finish = function finish() {
const len = Buffer.allocUnsafe(16);
let lo, hi;
// The RFC says these are supposed to be
// uint32le, but their own fucking test
// cases fail unless they are uint64le's.
lo = this.aadLen % 0x100000000;
hi = (this.aadLen - lo) / 0x100000000;
len.writeUInt32LE(lo, 0, true);
len.writeUInt32LE(hi, 4, true);
lo = this.cipherLen % 0x100000000;
hi = (this.cipherLen - lo) / 0x100000000;
len.writeUInt32LE(lo, 8, true);
len.writeUInt32LE(hi, 12, true);
if (this.cipherLen === 0)
this.pad16(this.aadLen);
this.pad16(this.cipherLen);
this.poly1305.update(len);
return this.poly1305.finish();
};
/**
* Pad a chunk before updating mac.
* @private
* @param {Number} size
*/
AEAD.prototype.pad16 = function pad16(size) {
size %= 16;
if (size === 0)
return;
const pad = Buffer.allocUnsafe(16 - size);
pad.fill(0);
this.poly1305.update(pad);
};
/*
* Expose
*/
module.exports = AEAD;