refactor aes.

This commit is contained in:
Christopher Jeffrey 2016-06-09 16:16:49 -07:00
parent 610ecde401
commit c84175dee1
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD

View File

@ -11,23 +11,26 @@
/* jshint latedef: false */ /* jshint latedef: false */
var assert = require('assert'); var assert = require('assert');
var U32Array = typeof Uint32Array === 'function' ? Uint32Array : Array;
var AES = exports;
/** /**
* An AES object for encrypting and decrypting blocks. * An AES key object for encrypting
* @exports AES * and decrypting blocks.
* @exports AESKey
* @constructor * @constructor
* @param {Buffer} key * @param {Buffer} key
* @param {Number} bits * @param {Number} bits
* @param {Buffer?} iv * @param {Buffer?} iv
*/ */
function AES(key, bits, iv) { function AESKey(key, bits) {
if (!(this instanceof AES)) if (!(this instanceof AESKey))
return new AES(key, bits, iv); return new AESKey(key, bits);
this.rounds = null;
this.userKey = key; this.userKey = key;
this.bits = bits; this.bits = bits;
this.iv = iv;
if (this.bits === 128) if (this.bits === 128)
this.rounds = 10; this.rounds = 10;
@ -46,19 +49,14 @@ function AES(key, bits, iv) {
* Destroy the object and zero the keys. * Destroy the object and zero the keys.
*/ */
AES.prototype.destroy = function destroy() { AESKey.prototype.destroy = function destroy() {
var i; var i;
assert(this.userKey, 'Already destroyed.'); assert(this.userKey, 'Already destroyed.');
this.userKey.fill(0); // User should zero this.
this.userKey = null; this.userKey = null;
if (this.iv) {
this.iv.fill(0);
this.iv = null;
}
if (this.decryptKey) { if (this.decryptKey) {
for (i = 0; i < this.decryptKey.length; i++) for (i = 0; i < this.decryptKey.length; i++)
this.decryptKey[i] = 0; this.decryptKey[i] = 0;
@ -74,19 +72,19 @@ AES.prototype.destroy = function destroy() {
/** /**
* Convert the user key into an encryption key. * Convert the user key into an encryption key.
* @returns {Object} key * @returns {Uint32Array} key
*/ */
AES.prototype.getEncryptKey = function getEncryptKey() { AESKey.prototype.getEncryptKey = function getEncryptKey() {
var i = 0; var i = 0;
var key, kp, tmp; var key, kp, tmp;
assert(this.userKey, 'Cannot use AES once it is destroyed.'); assert(this.userKey, 'Cannot use key once it is destroyed.');
if (this.encryptKey) if (this.encryptKey)
return this.encryptKey; return this.encryptKey;
key = []; key = new U32Array(60);
kp = 0; kp = 0;
key[kp + 0] = readU32(this.userKey, 0); key[kp + 0] = readU32(this.userKey, 0);
@ -183,21 +181,25 @@ AES.prototype.getEncryptKey = function getEncryptKey() {
/** /**
* Convert the user key into a decryption key. * Convert the user key into a decryption key.
* @returns {Object} key * @returns {Uint32Array} key
*/ */
AES.prototype.getDecryptKey = function getDecryptKey() { AESKey.prototype.getDecryptKey = function getDecryptKey() {
var i, j, kp, key, tmp; var i, j, kp, enc, key, tmp;
assert(this.userKey, 'Cannot use AES once it is destroyed.'); assert(this.userKey, 'Cannot use key once it is destroyed.');
if (this.decryptKey) if (this.decryptKey)
return this.decryptKey; return this.decryptKey;
// First, start with an encryption schedule. // First, start with an encryption schedule.
key = this.getEncryptKey().slice(); enc = this.getEncryptKey();
key = new U32Array(60);
kp = 0; kp = 0;
for (i = 0; i < enc.length; i++)
key[i] = enc[i];
this.decryptKey = key; this.decryptKey = key;
// Invert the order of the round keys. // Invert the order of the round keys.
@ -250,10 +252,10 @@ AES.prototype.getDecryptKey = function getDecryptKey() {
* @returns {Buffer} * @returns {Buffer}
*/ */
AES.prototype.encryptBlock = function encryptBlock(input) { AESKey.prototype.encryptBlock = function encryptBlock(input) {
var output, kp, key, r, s0, s1, s2, s3, t0, t1, t2, t3; var output, kp, key, r, s0, s1, s2, s3, t0, t1, t2, t3;
assert(this.userKey, 'Cannot use AES once it is destroyed.'); assert(this.userKey, 'Cannot use key once it is destroyed.');
key = this.getEncryptKey(); key = this.getEncryptKey();
kp = 0; kp = 0;
@ -355,10 +357,10 @@ AES.prototype.encryptBlock = function encryptBlock(input) {
* @returns {Buffer} * @returns {Buffer}
*/ */
AES.prototype.decryptBlock = function decryptBlock(input) { AESKey.prototype.decryptBlock = function decryptBlock(input) {
var output, kp, key, r, s0, s1, s2, s3, t0, t1, t2, t3; var output, kp, key, r, s0, s1, s2, s3, t0, t1, t2, t3;
assert(this.userKey, 'Cannot use AES once it is destroyed.'); assert(this.userKey, 'Cannot use AESKey once it is destroyed.');
key = this.getDecryptKey(); key = this.getDecryptKey();
kp = 0; kp = 0;
@ -455,40 +457,75 @@ AES.prototype.decryptBlock = function decryptBlock(input) {
}; };
/** /**
* Encrypt data with aes 256. * AES cipher.
* @param {Buffer} data * @exports AESCipher
* @constructor
* @param {Buffer} key * @param {Buffer} key
* @param {Buffer} iv * @param {Buffer} iv
* @param {Boolean?} chain - XOR chaining (cbc). * @param {Number} bits
* @param {String} mode
*/
function AESCipher(key, iv, bits, mode) {
if (!(this instanceof AESCipher))
return new AESCipher(key, iv, mode);
assert(mode === 'ecb' || mode === 'cbc', 'Unknown mode.');
this.key = new AESKey(key, bits);
this.mode = mode;
this.prev = iv;
this.waiting = null;
}
/**
* Encrypt blocks of data.
* @param {Buffer} data
* @returns {Buffer} * @returns {Buffer}
*/ */
AES.encrypt = function encrypt(data, key, iv, chain) { AESCipher.prototype.update = function update(data) {
var aes = new AES(key, 256, iv);
var trailing = data.length % 16;
var len = data.length - trailing;
var blocks = []; var blocks = [];
var i, prev, block, left, pad; var i, len, trailing, block;
// Setup initialization vector. if (this.waiting) {
prev = aes.iv; data = Buffer.concat([this.waiting, data]);
this.waiting = null;
}
trailing = data.length % 16;
len = data.length - trailing;
// Encrypt all blocks except for the last. // Encrypt all blocks except for the last.
for (i = 0; i < len; i += 16) { for (i = 0; i < len; i += 16) {
block = data.slice(i, i + 16); block = data.slice(i, i + 16);
if (chain) if (this.mode === 'cbc')
block = xor(block, prev); block = xor(block, this.prev);
prev = aes.encryptBlock(block); this.prev = this.key.encryptBlock(block);
blocks.push(prev); blocks.push(this.prev);
} }
if (trailing > 0)
this.waiting = data.slice(len);
return Buffer.concat(blocks);
};
/**
* Finalize the cipher.
* @returns {Buffer}
*/
AESCipher.prototype.final = function final() {
var i, block, left, pad;
// Handle padding on the last block. // Handle padding on the last block.
if (trailing === 0) { if (!this.waiting) {
block = new Buffer(16); block = new Buffer(16);
block.fill(16); block.fill(16);
} else { } else {
block = data.slice(len, len + trailing); block = this.waiting;
left = 16 - trailing; left = 16 - block.length;
pad = new Buffer(left); pad = new Buffer(left);
pad.fill(left); pad.fill(left);
block = Buffer.concat([block, pad]); block = Buffer.concat([block, pad]);
@ -496,70 +533,150 @@ AES.encrypt = function encrypt(data, key, iv, chain) {
// Encrypt the last block, // Encrypt the last block,
// as well as the padding. // as well as the padding.
if (chain) if (this.mode === 'cbc')
block = xor(block, prev); block = xor(block, this.prev);
block = aes.encryptBlock(block); block = this.key.encryptBlock(block);
blocks.push(block);
aes.destroy(); this.key.destroy();
return block;
};
/**
* AES decipher.
* @exports AESDecipher
* @constructor
* @param {Buffer} key
* @param {Buffer} iv
* @param {Number} bits
* @param {String} mode
*/
function AESDecipher(key, iv, bits, mode) {
if (!(this instanceof AESDecipher))
return new AESDecipher(key, iv, mode);
assert(mode === 'ecb' || mode === 'cbc', 'Unknown mode.');
this.key = new AESKey(key, bits);
this.mode = mode;
this.prev = iv;
this.waiting = null;
this.lastBlock = null;
}
/**
* Decrypt blocks of data.
* @param {Buffer} data
*/
AESDecipher.prototype.update = function update(data) {
var blocks = [];
var i, chunk, block, len, trailing;
if (this.waiting) {
data = Buffer.concat([this.waiting, data]);
this.waiting = null;
}
trailing = data.length % 16;
len = data.length - trailing;
// Decrypt all blocks.
for (i = 0; i < len; i += 16) {
chunk = this.prev;
this.prev = data.slice(i, i + 16);
block = this.key.decryptBlock(this.prev);
if (this.mode === 'cbc')
block = xor(block, chunk);
blocks.push(block);
}
if (trailing > 0)
this.waiting = data.slice(len);
if (this.lastBlock) {
blocks.unshift(this.lastBlock);
this.lastBlock = null;
}
// Keep a reference to the last
// block for the padding check.
this.lastBlock = blocks.pop();
return Buffer.concat(blocks); return Buffer.concat(blocks);
}; };
/**
* Finalize the decipher.
* @returns {Buffer}
*/
AESDecipher.prototype.final = function final() {
var i, b, n, block;
assert(!this.waiting, 'Bad decrypt (trailing bytes).');
assert(this.lastBlock, 'Bad decrypt (no data).');
// Check padding on the last block.
block = this.lastBlock;
b = 16;
n = block[b - 1];
if (n === 0 || n > b)
throw new Error('Bad decrypt (padding).');
for (i = 0; i < n; i++) {
if (block[--b] !== n)
throw new Error('Bad decrypt (padding).');
}
// Slice off the padding unless
// the entire block was padding.
if (n === 16)
return new Buffer(0);
block = block.slice(0, -n);
this.key.destroy();
return block;
};
/**
* Encrypt data with aes 256.
* @param {Buffer} data
* @param {Buffer} key
* @param {Buffer} iv
* @param {String} mode
* @returns {Buffer}
*/
AES.encrypt = function encrypt(data, key, iv, bits, mode) {
var cipher = new AESCipher(key, iv, bits, mode);
return Buffer.concat([
cipher.update(data),
cipher.final()
]);
};
/** /**
* Decrypt data with aes 256. * Decrypt data with aes 256.
* @param {Buffer} data * @param {Buffer} data
* @param {Buffer} key * @param {Buffer} key
* @param {Buffer} iv * @param {Buffer|null} iv
* @param {Boolean?} chain - XOR chaining (cbc). * @param {Number} bits
* @param {String} mode
* @returns {Buffer} * @returns {Buffer}
*/ */
AES.decrypt = function decrypt(data, key, iv, chain) { AES.decrypt = function decrypt(data, key, iv, bits, mode) {
var aes = new AES(key, 256, iv); var decipher = new AESDecipher(key, iv, bits, mode);
var blocks = []; return Buffer.concat([
var i, b, n, prev, chunk, block; decipher.update(data),
decipher.final()
assert(data.length > 0); ]);
assert(data.length % 16 === 0);
// Setup initialization vector.
prev = aes.iv;
// Decrypt all blocks.
for (i = 0; i < data.length; i += 16) {
chunk = prev;
prev = data.slice(i, i + 16);
block = aes.decryptBlock(prev);
if (chain)
block = xor(block, chunk);
blocks.push(block);
}
// Check padding on the last block.
block = blocks.pop();
b = 16;
n = block[b - 1];
if (n === 0 || n > b)
throw new Error('Bad decrypt');
for (i = 0; i < n; i++) {
if (block[--b] !== n)
throw new Error('Bad decrypt');
}
// Slice off the padding unless
// the entire block was padding.
if (n < 16) {
block = block.slice(0, -n);
blocks.push(block);
}
aes.destroy();
return Buffer.concat(blocks);
}; };
/** /**
@ -576,7 +693,7 @@ AES.ecb = {};
*/ */
AES.ecb.encrypt = function encrypt(data, key) { AES.ecb.encrypt = function encrypt(data, key) {
return AES.encrypt(data, key, null, false); return AES.encrypt(data, key, null, 256, 'ecb');
}; };
/** /**
@ -587,7 +704,7 @@ AES.ecb.encrypt = function encrypt(data, key) {
*/ */
AES.ecb.decrypt = function decrypt(data, key) { AES.ecb.decrypt = function decrypt(data, key) {
return AES.decrypt(data, key, null, false); return AES.decrypt(data, key, null, 256, 'ecb');
}; };
/** /**
@ -605,7 +722,7 @@ AES.cbc = {};
*/ */
AES.cbc.encrypt = function encrypt(data, key, iv) { AES.cbc.encrypt = function encrypt(data, key, iv) {
return AES.encrypt(data, key, iv, true); return AES.encrypt(data, key, iv, 256, 'cbc');
}; };
/** /**
@ -617,9 +734,17 @@ AES.cbc.encrypt = function encrypt(data, key, iv) {
*/ */
AES.cbc.decrypt = function decrypt(data, key, iv) { AES.cbc.decrypt = function decrypt(data, key, iv) {
return AES.decrypt(data, key, iv, true); return AES.decrypt(data, key, iv, 256, 'cbc');
}; };
/*
* Expose
*/
AES.Key = AESKey;
AES.Cipher = AESCipher;
AES.Decipher = AESDecipher;
/* /*
* Helpers * Helpers
*/ */
@ -1225,9 +1350,3 @@ var RCON = [
0x10000000, 0x20000000, 0x40000000, 0x80000000, 0x10000000, 0x20000000, 0x40000000, 0x80000000,
0x1B000000, 0x36000000 0x1B000000, 0x36000000
]; ];
/*
* Expose
*/
module.exports = AES;