123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- /*
- Most of this code was borrowed with permission from
- Cliford Symack - https://github.com/clifordsymack/
- */
- /* Import core modules. */
- const crypto = require('crypto')
- const PrivateKey = require('bitcore-lib-cash').PrivateKey
- const PublicKey = require('bitcore-lib-cash').PublicKey
- /**
- * AES Encrypt with IV
- */
- const _aesEncryptWithIV = function (key, iv, message) {
- let cipher, crypted
- cipher = crypto.createCipheriv('aes-128-cbc', key, iv)
- cipher.setAutoPadding(true)
- crypted = cipher.update(message, 'hex', 'hex')
- crypted += cipher.final('hex')
- return Buffer.from(crypted, 'hex')
- }
- /**
- * AES Decrypt with IV
- */
- const _aesDecryptWithIV = function (key, iv, message) {
- let cipher, crypted
- cipher = crypto.createDecipheriv('aes-128-cbc', key, iv)
- cipher.setAutoPadding(true)
- crypted = cipher.update(message, 'hex', 'hex')
- crypted += cipher.final('hex')
- return Buffer.from(crypted, 'hex')
- }
- /**
- * Rebuild Keypair
- *
- * TODO: Update `generateKeypair` for param and REMOVE THIS FUNCTION,
- * as it's never used in the codebase.
- */
- const rebuildKeypair = function (somePrivateKey) {
- /* Initialize keypair. */
- const keypair = {
- privateKey: new PrivateKey(somePrivateKey)
- }
- /* Set public key. */
- keypair.publicKey = keypair.privateKey.toPublicKey()
- /* Set public key (hex). */
- keypair.publicKeyHex = keypair.publicKey.toString('hex')
- /* Set private key (hex). */
- keypair.privateKeyHex = keypair.privateKey.toString('hex')
- /* Return keypair. */
- return keypair
- }
- /**
- * Generate Keypair
- *
- * FIXME: DRY this up, using `rebuildKeypair`.
- */
- const generateKeypair = function () {
- /* Initialize keypair. */
- const keypair = {
- privateKey: new PrivateKey()
- }
- /* Set public key. */
- keypair.publicKey = keypair.privateKey.toPublicKey()
- /* Set public key (hex). */
- keypair.publicKeyHex = keypair.publicKey.toString('hex')
- /* Set private key (hex). */
- keypair.privateKeyHex = keypair.privateKey.toString('hex')
- /* Return keypair. */
- return keypair
- }
- /**
- * Encrypt
- */
- const encrypt = function (plaintextMessage, pubkey) {
- /* Initialize public key. */
- const publicKey = PublicKey(pubkey)
- /* Initialize ephemeral (private key). */
- const ephemeral = new PrivateKey()
- /* Set ECDH key. */
- // NOTE: This is our shared secret with the pubkey holder.
- const ecdhKey = PublicKey(
- publicKey.point.mul(ephemeral.toBigNumber())).toBuffer()
- /* Set key. */
- // NOTE: We hash the shared secret as:
- // 1. First 16 bytes is the IV for AES.
- // 2. Second 16 bytes is the key for AES.
- // 3. Final 32 bytes are used for the HMAC.
- const key = crypto.createHash('sha512').update(ecdhKey).digest()
- /* Set ciphertext. */
- const ciphertext = _aesEncryptWithIV(
- key.slice(16, 32),
- key.slice(0, 16),
- Buffer.from(plaintextMessage, 'utf8')
- )
- /* Set encrypted. */
- // NOTE: We send the public key, for our ephemeral private key,
- // as an unencrypted buffer, as part of the prefix to our
- // encrypted package. This allows ONLY the `pubkey` holder to
- // decrypt our (ciphertext) message.
- const encrypted = Buffer.concat([
- Buffer.from('BIE1'),
- ephemeral.publicKey.toBuffer(),
- ciphertext
- ])
- /* Set MAC. */
- const mac = crypto
- .createHmac('sha256', key.slice(32))
- .update(encrypted)
- .digest()
- /* Return encrypted. */
- return Buffer.concat([encrypted, mac]).toString('base64')
- }
- /**
- * Decrypt
- */
- const decrypt = function (encryptedMessage, somePrivateKeyHexString) {
- /* Initialize private key. */
- const privateKey = new PrivateKey(somePrivateKeyHexString)
- /* Initialize encrypted (message). */
- const encrypted = Buffer.from(encryptedMessage, 'base64')
- /* Valiate encrypted (message). */
- if (encrypted.length < 85) {
- throw new Error('invalid ciphertext: length')
- }
- /* Set magic (bytes). */
- const magic = encrypted.slice(0, 4)
- /* Initialize ephemeral public key. */
- let ephemeralPubkey = encrypted.slice(4, 37)
- /* Set ciphertext. */
- const ciphertext = encrypted.slice(37, -32)
- /* Set MAC. */
- const mac = encrypted.slice(-32)
- /* Validate magic (bytes). */
- if (magic.toString() !== 'BIE1') {
- throw new Error('invalid ciphertext: invalid magic bytes')
- }
- try {
- /* Set ephemeral public key. */
- ephemeralPubkey = PublicKey(ephemeralPubkey)
- } catch (error) {
- throw new Error('invalid ciphertext: invalid ephemeral pubkey')
- }
- // ???
- ephemeralPubkey.point.validate()
- /* Set secret multiplier. */
- const secretMultiplier = privateKey.toBigNumber()
- /* Set ECDH key. */
- const ecdhKey = PublicKey(
- ephemeralPubkey.point.mul(secretMultiplier)).toBuffer()
- /* Set key. */
- const key = crypto.createHash('sha512').update(ecdhKey).digest()
- /* Set initialization vector. */
- const iv = key.slice(0, 16)
- /* Set key (E). */
- const keyE = key.slice(16, 32)
- /* Set key (M). */
- const keyM = key.slice(32)
- /* Calculate the "valid" MAC. */
- const validMac = crypto
- .createHmac('sha256', keyM)
- .update(encrypted.slice(0, -32))
- // .update(encrypted.slice(-32)) // THIS SHOULD WORK AS WELL
- .digest('hex')
- /* Validate MAC. */
- if (mac.toString('hex') !== validMac) {
- throw new Error('invalid password')
- }
- /* Return decrypted string. */
- return _aesDecryptWithIV(keyE, iv, ciphertext)
- }
- /**
- * Hash
- *
- * FIXME: DO WE STILL NEED THIS FUNCTION??
- * IT'S NOT BEING USED ANYWHERE IN THE CODEBASE
- */
- const hash = function (text, algorithm) {
- /* Initialize algorithm. */
- // algorithm = algorithm || 'sha224'
- algorithm = algorithm || 'sha256'
- try {
- return crypto
- .createHash(algorithm)
- .update(Buffer.from(text), 'utf8')
- .digest()
- } catch (error) {
- return null
- }
- }
- /* Export module. */
- module.exports = {
- rebuildKeypair,
- generateKeypair,
- encrypt,
- decrypt,
- hash
- }
|