hybrid-ecies
Version:
Hybrid EC encryption scheme that EC curve secp256k1, and chacha20-poly1305 or aes-256-gcm to encrypt data. The returned data is a packed Buffer with the public key, nonce/iv, tag, and encrypted data.
328 lines (286 loc) • 11.6 kB
text/typescript
import { createECDH, ECDH, createCipheriv, createDecipheriv, randomFillSync, createHash } from 'crypto';
import { strict } from 'assert';
import { stringify } from 'querystring';
/**
* JSON Wek Token
*/
export interface JWK {
"kty": string;
"d"?: string;
"crv": string;
"kid": string;
"x": string;
"y"?: string;
}
/**
* Hybrid EC encryption scheme that EC curve secp256k1, and chacha20-poly1305 or aes-256-gcm to encrypt data.
* The returned data is a packed Buffer with the public key, nonce/iv, tag, and encrypted data.
*/
export class ECIES {
/**
* This creates a EC secp256k1 key pair and returns the private key as a buffer.
* @returns EC Private Key as a Buffer
*/
createKeyPair(): Buffer {
let ec: ECDH = createECDH('secp256k1');
ec.generateKeys();
return ec.getPrivateKey();
}
/**
* This returns the calculated secret from a private and public key.
*
* @param privateKey: Buffer
* @param publicKey: Buffer
* @returns secret
*/
getSecret(privateKey: Buffer, publicKey: Buffer): Buffer {
let ec: ECDH = createECDH('secp256k1');
ec.setPrivateKey(privateKey);
return ec.computeSecret(publicKey);
}
/**
* Takes EC private key and returns the public key.
*
* @param privateKey EC Private Key
* @param compress If true return only the x value
* @returns publicKey X,Y buffer
*/
getPublicKey(privateKey: Buffer, compress?: Boolean): Buffer {
let ec: ECDH = createECDH('secp256k1');
ec.setPrivateKey(privateKey);
// console.log('pub', ec.getPublicKey('hex'));
// console.log('pub',Buffer.from(ec.getPublicKey('latin1'), 'latin1').toString('hex'));
return (compress === true ? Buffer.from(ec.getPublicKey('hex','compressed'), 'hex') : ec.getPublicKey());
}
/**
* This takes an EC private key and returns the JWK.
*
* @param privateKey EC private key
* @returns Json Web Token
*/
privateJWK(privateKey: Buffer): JWK {
let ec: ECDH = createECDH('secp256k1');
ec.setPrivateKey(privateKey);
let jwk = this.publicJWK(ec.getPublicKey());
jwk.d = privateKey.toString('base64');
return jwk;
}
/**
* This takes an EC public key and returns the JWK.
*
* @param publicKey EC Public Key
* @returns Json Web Token
*/
publicJWK(publicKey: Buffer): JWK {
let x:string;
let y:string;
let jwk: JWK = {
"kty": "EC",
"crv": "secp256k1",
"kid": "1",
"x": ""
}
switch (publicKey.length) {
case 33:
jwk.x = publicKey.toString('base64');
break;
case 65:
var bufX = Buffer.alloc(32);
var bufY = Buffer.alloc(32);
publicKey.copy(bufX,0,1,33);
publicKey.copy(bufY,0,33);
jwk.x = bufX.toString('base64');
jwk.y = bufY.toString('base64');
break;
case 64:
bufX = Buffer.alloc(32);
bufY = Buffer.alloc(32);
publicKey.copy(bufX,0,0,32);
publicKey.copy(bufY,0,32);
jwk.x = bufX.toString('base64');
jwk.y = bufY.toString('base64');
break;
default:
let err = new Error('Invalid Key');
err.name = 'Invalid_Key';
throw err;
}
jwk.kid = createHash('sha256').update(publicKey).digest().toString('base64');
return jwk;
}
/**
* Return a Buffer from either a public or private JWK.
*
* @param jwk public or private JSON Web Key
* @returns Buffer of either public or private key
*/
JWKtoBuffer(jwk: JWK ): Buffer {
if(jwk.d) {
return Buffer.from(jwk.d, 'base64');
} else if (jwk.y) {
return Buffer.concat([Buffer.alloc(1, 0x04),Buffer.from(jwk.x,'base64'),Buffer.from(jwk.y,'base64')]);
} else {
return Buffer.from(jwk.x,'base64');
}
}
getPEM(ecKey: Buffer, encoding: 'RAW' | 'DER' ,type: 'Private' | 'Public'): string {
let PEM: string = '';
let pemStr = '';
(encoding === 'RAW' ? pemStr = this.getDER(ecKey,type).toString('base64') : pemStr = ecKey.toString('base64'));
// console.log(pemStr);
let pemForm:string = '';
let i = 0;
let c = 64;
do {
var j = i + 64;
(j >= pemStr.length ? c = pemStr.length - i : c = 64);
pemForm = `${pemForm}${pemStr.substr(i,c)}\n`;
i = j;
// console.log(i);
} while (i < pemStr.length);
if(type === "Private") {
PEM = `-----BEGIN EC PRIVATE KEY-----\n${pemForm}----END EC PRIVATE KEY-----`
} else {
PEM = `-----BEGIN PUBLIC KEY-----\n${pemForm}-----END PUBLIC KEY-----`
}
// console.log(PEM);
return PEM;
}
getDER(ecKey: Buffer, type: 'Private' | 'Public'): Buffer {
let packDER: Buffer;
if(type === 'Private') {
packDER = Buffer.concat([Buffer.from('30740201010420','hex'),ecKey,Buffer.from('a00706052b8104000aa144034200','hex'),this.getPublicKey(ecKey)]);
} else {
let pre: string;
(ecKey.length === 33 ? (pre = '3036301006072a8648ce3d020106052b8104000a032200') : (pre = '3056301006072a8648ce3d020106052b8104000a034200'));
if(ecKey.length > 65)
throw new Error('Invalid key');
packDER = Buffer.concat([Buffer.from(pre,'hex'),ecKey]);
}
return packDER;
}
/**
* This takes an EC public key as input, creates an unique EC pair to encrypt the data.
* Returns a packed buffer of the EC public key, nonce, tag, and encrypted data.
* Optional to supply Private Key
* @param publicKey EC Public Key
* @param privateKey Optional
* @param data Data to encrypt
* @returns Buffer(Bytes) - ECPubKey(33) iv(12) tag(16) encData(variable)
*/
encryptAES256(publicKey: Buffer, data: Buffer): Buffer
encryptAES256(publicKey: Buffer, privateKey: Buffer, data: Buffer): Buffer
encryptAES256(publicKey: Buffer, arg2: Buffer, arg3?: Buffer): Buffer {
let privateKey: Buffer;
let data: Buffer;
if(arg2 && arg3) {
privateKey = arg2;
data = arg3;
} else {
privateKey = this.createKeyPair();
data = arg2;
}
let iv = Buffer.alloc(12);
randomFillSync(iv);
// console.log('nonce', nonce.toString('hex'));
let key = this.getSecret(privateKey,publicKey);
// console.log('key', key.toString('hex'));
let aes = createCipheriv('aes-256-gcm',key,iv);
let encData = aes.update(data);
aes.final();
let tag = aes.getAuthTag();
let pack = Buffer.concat([this.getPublicKey(privateKey,true),iv,tag,encData]);
return pack;
}
/**
* Takes private EC key of the public key used to encrypt the data and decrypts it.
*
* @param privateKey EC Key used to encrypt the data.
* @param encodedData Buffer(Bytes) - ECPubKey(33) iv(12) tag(16) encData(variable)
* @returns Buffer of decrypted data.
*/
decryptAES256(privateKey: Buffer, encodedData: Buffer): Buffer {
let pubKey = Buffer.alloc(33);
encodedData.copy(pubKey,0,0,33);
let iv = Buffer.alloc(12);
encodedData.copy(iv,0,33,(33+12));
let tag = Buffer.alloc(16);
encodedData.copy(tag,0,(33+12),(33+12+16));
let encData = Buffer.alloc(encodedData.length-(33+12+16));
encodedData.copy(encData,0,(33+12+16));
let key = this.getSecret(privateKey,pubKey);
// console.log('key', key.toString('hex'));
let aes = createDecipheriv('aes-256-gcm',key,iv);
aes.setAuthTag(tag);
let data = aes.update(encData);
aes.final();
return data;
}
/**
* This takes an EC public key as input, creates an EC pair to encrypt the data.
* Returns a packed buffer of the EC public key, nonce, tag, and encrypted data.
* Optional to supply Private Key
* @param publicKey EC Public Key
* @param privateKey Optional
* @param data Data to encrypt
* @returns Buffer(Bytes) - ECPubKey(33) nonce(12) tag(16) encData(variable)
*/
encryptChaCha20(publicKey: Buffer, data: any): Buffer
encryptChaCha20(publicKey: Buffer, privateKey: Buffer, data: any): Buffer
encryptChaCha20(publicKey: Buffer, arg2: any, arg3?: any): Buffer {
let privateKey: Buffer;
let data: Buffer;
if(arg2 && arg3) {
privateKey = arg2;
data = arg3;
} else {
privateKey = this.createKeyPair();
data = arg2;
}
let nonce = Buffer.alloc(12);
randomFillSync(nonce);
// console.log('nonce', nonce.toString('hex'));
// let tempKey: Buffer = Buffer.alloc(0);
let key = this.getSecret(privateKey,publicKey);
// console.log('key', key.toString('hex'));
let cipher = createCipheriv('chacha20-poly1305', key, nonce, { authTagLength: 16 });
let encData = cipher.update(data);
cipher.final();
let tag = cipher.getAuthTag();
// console.log('data enc ', encData.toString('hex'));
// console.log('tag', tag.toString('hex'));
// console.log('enc pub', this.getPublicKey(tempKey, true).toString('hex'));
let pack = Buffer.concat([this.getPublicKey(privateKey,true),nonce,tag,encData]);
// console.log(pack.toString('hex'));
// console.log(pack.toString('base64'));
return pack;
}
/**
* Takes private EC key of the public key used to encrypt the data and decrypts it.
*
* @param privateKey EC Key used to encrypt the data.
* @param encodedData Buffer(Bytes) - ECPubKey(33) nonce(12) tag(16) encData(variable)
* @returns Buffer of decrypted data.
*/
decryptChaCha20(privateKey: Buffer, encodedData: Buffer): Buffer {
let pubKey = Buffer.alloc(33);
encodedData.copy(pubKey,0,0,33);
let nonce = Buffer.alloc(12);
encodedData.copy(nonce,0,33,(33+12));
let tag = Buffer.alloc(16);
encodedData.copy(tag,0,(33+12),(33+12+16));
let data = Buffer.alloc(encodedData.length-(33+12+16));
encodedData.copy(data,0,(33+12+16));
let key = this.getSecret(privateKey,pubKey);
// console.log('key', key.toString('hex'));
// console.log('pubKey', pubKey.toString('hex'));
// console.log('nonce', nonce.toString('hex'));
// console.log('tag', tag.toString('hex'));
let dec = createDecipheriv('chacha20-poly1305', key, nonce, { authTagLength: 16 });
dec.setAuthTag(tag);
let decData = dec.update(data);
dec.final();
// console.log('mdg', decData.toString());
return decData;
}
}