-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat({encrypt,decrypt}.js): Introduce encryption/decryption functions
- Loading branch information
Showing
2 changed files
with
204 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
'use strict' | ||
|
||
const { createDecipheriv, createHmac } = require('crypto') | ||
const isBuffer = require('is-buffer') | ||
const uint64 = require('./uint64') | ||
const pkg = require('./package') | ||
|
||
const { | ||
kVersion, | ||
kDefaultCipher, | ||
kDefaultDigest, | ||
} = require('./constants') | ||
|
||
/** | ||
* Decrypt an encrypted "crypto" object into the originally | ||
* encoded buffer. | ||
* @public | ||
* @param {Object} value | ||
* @param {Object} opts | ||
* @param {Buffer} opts.iv | ||
* @param {Buffer} opts.key | ||
* @param {?(String)} opts.cipher | ||
* @param {?(String)} opts.digest | ||
* @return {Buffer} | ||
*/ | ||
function decrypt(value, opts) { | ||
if (null == value) { | ||
throw new TypeError("crypto.decrypt: Encrypted value cannot be null.") | ||
} else if (isBuffer(value)) { | ||
throw new TypeError("crypto.decrypt: Encrypted value cannot be a buffer.") | ||
} else if (!value || 'object' != typeof value){ | ||
throw new TypeError("crypto.decrypt: Encrypted value to be an object.") | ||
} | ||
|
||
if (null == value.version || 'string' != typeof value.version) { | ||
throw new TypeError("crypto.decrypt: Missing encryption version.") | ||
} | ||
|
||
if (null == value.id || 'string' != typeof value.id) { | ||
throw new TypeError("crypto.decrypt: Missing encryption ID.") | ||
} | ||
|
||
if (null == value.crypto || 'object' != typeof value.crypto) { | ||
throw new TypeError( | ||
"crypto.decrypt: Missing encryption crypto specification object.") | ||
} | ||
|
||
if (!opts || 'object' != typeof opts) { | ||
throw new TypeError("crypto.decrypt: Expecting options object.") | ||
} | ||
|
||
if (null == opts.key) { | ||
throw new TypeError("crypto.decrypt: Expecting decryption key.") | ||
} else if ('string' != typeof opts.key && false == isBuffer(opts.key)) { | ||
throw new TypeError( | ||
"crypto.decrypt: Expecting decryption key to be a string or buffer.") | ||
} | ||
|
||
if (null == opts.iv) { | ||
throw new TypeError("crypto.decrypt: Expecting decryption iv.") | ||
} else if ('string' != typeof opts.iv && false == isBuffer(opts.iv)) { | ||
throw new TypeError( | ||
"crypto.decrypt: Expecting decryption iv to be a string or buffer.") | ||
} | ||
|
||
if (!opts.cipher || 'string' != typeof opts.cipher) { | ||
opts.cipher = kDefaultCipher | ||
} | ||
|
||
if (!opts.digest || 'string' != typeof opts.digest) { | ||
opts.digest = kDefaultDigest | ||
} | ||
|
||
if ('string' != typeof opts.cipher) { | ||
throw new TypeError("crypto.decrypt: Expecting cipher type to be a string.") | ||
} | ||
|
||
if ('string' != typeof opts.digest) { | ||
throw new TypeError("crypto.decrypt: Expecting digest type to be a string.") | ||
} | ||
|
||
const version = uint64.encode(kVersion).toString('hex') | ||
if (version != value.version) { | ||
throw new TypeError("crypto.decrypt: Encryption version does not match.") | ||
} | ||
|
||
const { cipher, digest, key, iv } = opts | ||
const decipheriv = createDecipheriv(cipher, key, iv) | ||
const hmac = createHmac(digest, key) | ||
const buffer = Buffer.from(value.crypto.ciphertext, 'hex') | ||
|
||
hmac.write(buffer) | ||
hmac.end() | ||
|
||
const mac = hmac.read().toString('hex') | ||
|
||
if (mac != value.crypto.mac) { | ||
throw new TypeError("crypto.decrypt: HMAC digest does not match.") | ||
} | ||
|
||
return Buffer.concat([ | ||
decipheriv.update(buffer), | ||
decipheriv.final(), | ||
]) | ||
} | ||
|
||
module.exports = { | ||
decrypt | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
'use strict' | ||
|
||
const { createCipheriv, createHmac } = require('crypto') | ||
const toBuffer = require('to-buffer') | ||
const isBuffer = require('is-buffer') | ||
const uint64 = require('./uint64') | ||
const uuid = require('uuid/v4') | ||
|
||
const { | ||
kVersion, | ||
kDefaultCipher, | ||
kDefaultDigest, | ||
} = require('./constants') | ||
|
||
/** | ||
* Encrypts value into a "crypto" object configured by | ||
* an initialization vector (iv) and secret key (key) with | ||
* optional cipher and digest algorithms. | ||
* @public | ||
* @param {String|Buffer} value | ||
* @param {Object} opts | ||
* @param {Buffer} opts.iv | ||
* @param {Buffer} opts.key | ||
* @param {?(String)} opts.cipher | ||
* @param {?(String)} opts.digest | ||
* @return {Object} | ||
*/ | ||
function encrypt(value, opts) { | ||
if (null == value) { | ||
throw new TypeError("crypto.encrypt: Expecting value. Got null.") | ||
} else if ('string' != typeof value && false == isBuffer(value)) { | ||
throw new TypeError( | ||
"crypto.encrypt: Expecting value to be a string or buffer.") | ||
} else if (0 == value.length) { | ||
throw new TypeError( | ||
"crypto.encrypt: Cannot encrypt empty value.") | ||
} | ||
|
||
if (!opts || 'object' != typeof opts) { | ||
throw new TypeError("crypto.encrypt: Expecting options object.") | ||
} | ||
|
||
if (null == opts.key) { | ||
throw new TypeError("crypto.encrypt: Expecting encryption key.") | ||
} else if ('string' != typeof opts.key && false == isBuffer(opts.key)) { | ||
throw new TypeError( | ||
"crypto.encrypt: Expecting encryption key to be a string or buffer.") | ||
} | ||
|
||
if (!opts.cipher || 'string' != typeof opts.cipher) { | ||
opts.cipher = kDefaultCipher | ||
} | ||
|
||
if (!opts.digest || 'string' != typeof opts.digest) { | ||
opts.digest = kDefaultDigest | ||
} | ||
|
||
if ('string' != typeof opts.cipher) { | ||
throw new TypeError("crypto.encrypt: Expecting cipher type to be a string.") | ||
} | ||
|
||
if ('string' != typeof opts.digest) { | ||
throw new TypeError("crypto.encrypt: Expecting digest type to be a string.") | ||
} | ||
|
||
const { cipher, digest, key, iv } = opts | ||
const cipheriv = createCipheriv(cipher, key, iv) | ||
const hmac = createHmac(digest, key) | ||
|
||
const buffer = Buffer.concat([cipheriv.update(value), cipheriv.final()]) | ||
const ciphertext = buffer.toString('hex') | ||
|
||
hmac.write(buffer) | ||
hmac.end() | ||
|
||
const id = uuid() | ||
const mac = hmac.read().toString('hex') | ||
const version = uint64.encode(kVersion).toString('hex') | ||
const cipherparams = { iv: iv.toString('hex') } | ||
|
||
return { | ||
id, | ||
version, | ||
crypto: { | ||
cipherparams, | ||
ciphertext, | ||
cipher, | ||
mac, | ||
} | ||
} | ||
} | ||
|
||
module.exports = { | ||
encrypt | ||
} |