173 lines
3.4 KiB
JavaScript
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;
|