Skip to content

Commit

Permalink
feat({encrypt,decrypt}.js): Introduce encryption/decryption functions
Browse files Browse the repository at this point in the history
  • Loading branch information
jwerle committed May 22, 2018
1 parent 2744596 commit 3fc4ffb
Show file tree
Hide file tree
Showing 2 changed files with 204 additions and 0 deletions.
109 changes: 109 additions & 0 deletions decrypt.js
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
}
95 changes: 95 additions & 0 deletions encrypt.js
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
}

0 comments on commit 3fc4ffb

Please sign in to comment.