ecies-lite
Version:
A lightweight ECIES tool implemented in pure Node.JS
93 lines (86 loc) • 4.22 kB
JavaScript
const crypto = require('crypto');
const config = {
curveName: 'secp256k1',
cipherAlgorithm: 'aes-256-cbc',
hmacAlgorithm: 'sha256',
ivSize: 16,
cipherKeyGen: (bytes) => bytes.slice(0, 32),
hmacKeyGen: (bytes) => bytes.slice(16)
};
/**
* Config the default parameters for ecies-lite
* @param curveName: string | object - the elliptic curve to use | the config object contains config items
* @param cipherAlgorithm?: string - the cipher algorithm to use
* @param hmacAlgorithm?: string - the hmac algorithm to use
* @param ivSize?: number - the size (in bytes) of initialization vector (for cipher)
* @param cipherKeyGen?: (Buffer) -> Buffer - the cipher key generator
* @param hmacKeyGen?: (Buffer) -> Buffer - the hmac key generator
* @return none
*/
exports.config = (curveName, cipherAlgorithm, hmacAlgorithm, ivSize, cipherKeyGen, hmacKeyGen) => {
if (typeof curveName === 'string') {
config.curveName = curveName || config.curveName;
config.cipherAlgorithm = cipherAlgorithm || config.cipherAlgorithm;
config.hmacAlgorithm = hmacAlgorithm || config.hmacAlgorithm;
config.ivSize = ivSize || config.ivSize;
config.cipherKeyGen = cipherKeyGen || config.cipherKeyGen;
config.hmacKeyGen = hmacKeyGen || config.hmacKeyGen;
}
else if (curveName instanceof Object) {
Object.assign(config, curveName);
}
};
/**
* Encrypt a message using the recepient's public key
* @param pk: Buffer - The recipient's public key
* @param msg: Buffer - The message to encrypt
* @param opts?: the same structure as the config object - you can use it to specify advanced options
* @return {epk: Buffer, iv: Buffer, ct: Buffer, mac: Buffer} - the ecies-lite structured object with fields correspondingly stands for ephemeral public key, initialization vector, cipher text, mac code for above data, etc.
*/
exports.encrypt = (pk, msg, opts) => {
let ctx = Object.assign({}, config);
ctx = Object.assign(ctx, opts || {});
const ecdh = crypto.createECDH(ctx.curveName);
if (ctx.esk) {
ecdh.setPrivateKey(ctx.esk);
} else {
ecdh.generateKeys();
}
const epk = ecdh.getPublicKey(null, ctx.compressEpk ? 'compressed' : 'uncompressed');
with (ctx) {
const hash = crypto.createHash('sha256').update(ecdh.computeSecret(pk)).digest();
const cipherKey = cipherKeyGen(hash), macKey = hmacKeyGen(hash);
const iv = ctx.iv || crypto.randomBytes(ivSize);
const cipher = crypto.createCipheriv(cipherAlgorithm, cipherKey, iv);
let ct = cipher.update(msg);
ct = Buffer.concat([ct, cipher.final()]);
const mac = crypto.createHmac(hmacAlgorithm, macKey).update(Buffer.concat([epk, iv, ct])).digest();
return {epk, iv, ct, mac};
}
};
/**
* Decrypt a message in ecies-lite defined format using the recipient's private key
* @param sk: Buffer - the recepient's private key
* @param body: ecies-lite structured object - the ecies-lite body (seen format in encrypt) to decrypt
* @param opts?: the same structure as the config object - you can use it to specify advanced options
* @return Buffer - the plain text decrypted from the Ecies-lite body
* @throws when mac value is unmatched, it throws 'Corrupted Ecies-lite body: unmatched authentication code' error
*/
exports.decrypt = (sk, body, opts) => {
let ctx = Object.assign({}, config);
ctx = Object.assign(ctx, opts || {});
ctx = Object.assign(ctx, body);
with (ctx) {
const ecdh = crypto.createECDH(curveName);
ecdh.setPrivateKey(sk);
const hash = crypto.createHash('sha256').update(ecdh.computeSecret(epk)).digest();
const cipherKey = cipherKeyGen(hash), macKey = hmacKeyGen(hash);
const m = crypto.createHmac(hmacAlgorithm, macKey).update(Buffer.concat([epk, iv, ct])).digest();
if (m.compare(mac) !== 0 || mac.compare(m) !== 0) {
throw new Error('Corrupted Ecies-lite body: unmatched authentication code');
}
const decipher = crypto.createDecipheriv(cipherAlgorithm, cipherKey, iv);
let pt = decipher.update(ct);
return Buffer.concat([pt, decipher.final()]);
}
};