refactor aes.
This commit is contained in:
parent
610ecde401
commit
c84175dee1
327
lib/bcoin/aes.js
327
lib/bcoin/aes.js
@ -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;
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user