blockstack
Version:
The Blockstack Javascript library for identity and authentication.
136 lines (110 loc) • 4.8 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getHexFromBN = getHexFromBN;
exports.encryptECIES = encryptECIES;
exports.decryptECIES = decryptECIES;
var _elliptic = require('elliptic');
var _crypto = require('crypto');
var _crypto2 = _interopRequireDefault(_crypto);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var ecurve = new _elliptic.ec('secp256k1');
function aes256CbcEncrypt(iv, key, plaintext) {
var cipher = _crypto2.default.createCipheriv('aes-256-cbc', key, iv);
return Buffer.concat([cipher.update(plaintext), cipher.final()]);
}
function aes256CbcDecrypt(iv, key, ciphertext) {
var cipher = _crypto2.default.createDecipheriv('aes-256-cbc', key, iv);
return Buffer.concat([cipher.update(ciphertext), cipher.final()]);
}
function hmacSha256(key, content) {
return _crypto2.default.createHmac('sha256', key).update(content).digest();
}
function equalConstTime(b1, b2) {
if (b1.length !== b2.length) {
return false;
}
var res = 0;
for (var i = 0; i < b1.length; i++) {
res |= b1[i] ^ b2[i]; // jshint ignore:line
}
return res === 0;
}
function sharedSecretToKeys(sharedSecret) {
// generate mac and encryption key from shared secret
var hashedSecret = _crypto2.default.createHash('sha512').update(sharedSecret).digest();
return { encryptionKey: hashedSecret.slice(0, 32),
hmacKey: hashedSecret.slice(32) };
}
function getHexFromBN(bnInput) {
var hexOut = bnInput.toString('hex');
if (hexOut.length === 64) {
return hexOut;
} else if (hexOut.length < 64) {
// pad with leading zeros
// the padStart function would require node 9
var padding = '0'.repeat(64 - hexOut.length);
return '' + padding + hexOut;
} else {
throw new Error('Generated a > 32-byte BN for encryption. Failing.');
}
}
/**
* Encrypt content to elliptic curve publicKey using ECIES
* @param {String} publicKey - secp256k1 public key hex string
* @param {String | Buffer} content - content to encrypt
* @return {Object} Object containing (hex encoded):
* iv (initialization vector), cipherText (cipher text),
* mac (message authentication code), ephemeral public key
* wasString (boolean indicating with or not to return a buffer or string on decrypt)
*/
function encryptECIES(publicKey, content) {
var isString = typeof content === 'string';
var plainText = new Buffer(content); // always copy to buffer
var ecPK = ecurve.keyFromPublic(publicKey, 'hex').getPublic();
var ephemeralSK = ecurve.genKeyPair();
var ephemeralPK = ephemeralSK.getPublic();
var sharedSecret = ephemeralSK.derive(ecPK);
var sharedSecretHex = getHexFromBN(sharedSecret);
var sharedKeys = sharedSecretToKeys(new Buffer(sharedSecretHex, 'hex'));
var initializationVector = _crypto2.default.randomBytes(16);
var cipherText = aes256CbcEncrypt(initializationVector, sharedKeys.encryptionKey, plainText);
var macData = Buffer.concat([initializationVector, new Buffer(ephemeralPK.encodeCompressed()), cipherText]);
var mac = hmacSha256(sharedKeys.hmacKey, macData);
return { iv: initializationVector.toString('hex'),
ephemeralPK: ephemeralPK.encodeCompressed('hex'),
cipherText: cipherText.toString('hex'),
mac: mac.toString('hex'),
wasString: isString };
}
/**
* Decrypt content encrypted using ECIES
* @param {String} privateKey - secp256k1 private key hex string
* @param {Object} cipherObject - object to decrypt, should contain:
* iv (initialization vector), cipherText (cipher text),
* mac (message authentication code), ephemeralPublicKey
* wasString (boolean indicating with or not to return a buffer or string on decrypt)
* @return {Buffer} plaintext, or false if error
*/
function decryptECIES(privateKey, cipherObject) {
var ecSK = ecurve.keyFromPrivate(privateKey, 'hex');
var ephemeralPK = ecurve.keyFromPublic(cipherObject.ephemeralPK, 'hex').getPublic();
var sharedSecret = ecSK.derive(ephemeralPK);
var sharedSecretBuffer = new Buffer(getHexFromBN(sharedSecret), 'hex');
var sharedKeys = sharedSecretToKeys(sharedSecretBuffer);
var ivBuffer = new Buffer(cipherObject.iv, 'hex');
var cipherTextBuffer = new Buffer(cipherObject.cipherText, 'hex');
var macData = Buffer.concat([ivBuffer, new Buffer(ephemeralPK.encodeCompressed()), cipherTextBuffer]);
var actualMac = hmacSha256(sharedKeys.hmacKey, macData);
var expectedMac = new Buffer(cipherObject.mac, 'hex');
if (!equalConstTime(expectedMac, actualMac)) {
throw new Error('Decryption failed: failure in MAC check');
}
var plainText = aes256CbcDecrypt(ivBuffer, sharedKeys.encryptionKey, cipherTextBuffer);
if (cipherObject.wasString) {
return plainText.toString();
} else {
return plainText;
}
}