src/discretecrypt.js
const scrypt = require('scrypt-async')
const bigInt = require('bn.js')
const aesjs = require('aes-js')
const jsSHA = require('jssha')
// Applied in Password-Based cases.
const DEFAULT_SCRYPT_CONFIG = {
N: 1 << 14,
r: 10,
p: 3,
len: 64
}
// Should be applied for performance reasons when the author is willing to accept
// slightly reduced security for performance reasons.
// This can securely be applied in situations where password input is reasonably decent.
const TUNED_SCRYPT_CONFIG = {
N: 1 << 14,
r: 11,
p: 1,
len: 48
}
// This should be applied in cases where the key doesn't need much stretching.
// Usually due to ephemeral keys
const EPHEMERAL_SCRYPT_CONFIG = {
N: 1 << 10,
r: 4,
p: 1,
len: 32
}
// Funnily enough, it seems the default params can have their pohlig found in 7.
const DEFAULT_PARAMS = {
prime: '1236027852723267358067496240415081192016632901798652377386974104662393263762300791015297301419782476103015366958792837873764932552461292791165884073898812814414137342163134112441573878695866548152604326906481241134560091096795607547486746060322717834549300353793656273878542405925895784382400028374603183267116520399667622873636417533621785188753096887486165751218947390793886174932206305484313257628695734926449809428884085464402485504798782585345665225579018127843073619788513405272670558284073983759985451287742892999484270521626583252756445695489268987027078838378407733148367649564107237496006094048593708959670063677802988307113944522310326616125731276572628521088574537964296697257866765026848588469121515995674723869067535040253689232576404893685613618463095967906841853447414047313021676108205138971649482561844148237707440562831931089544088821151806962538015278155763187487878945694840272084274212918033049841007502061',
gen: '2'
}
/**
* This assumes that this is a Nearly Safe Prime, with factors under 4096 (default).
* @private
* @param {BigInt|bigInt|string} prime
* @param {Number=} range The largest allowed prime factor (aside from the pohlig itself)
*/
function pohlig(prime, range)
{
// checks the range
if(typeof range !== "undefined" && typeof range !== "number")
{
throw "Only number types are allowed for the range parameter."
}
/* istanbul ignore if */
if (typeof BigInt !== "undefined")
{
/*
* This assumes that this is a Nearly Safe Prime, with factors under 4096 (default).
* @param {BigInt|bigInt|string} prime
* @param {Number=} range The largest allowed prime factor (aside from the pohlig itself)
*/
function native_pohlig(prime, range)
{
if (typeof prime === "string") prime = BigInt(prime)
else if (typeof prime !== "bigint") prime = BigInt(prime.toString())
prime -= BigInt(1)
let factors = BigInt(1)
let max = BigInt((range || (1 << 12)) + 1)
/* istanbul ignore next */
for (let i = BigInt(2); i < max; i++)
{
while (!(prime % i))
{
prime /= i
factors *= i
}
}
return [prime.toString(), factors.toString()]
}
return native_pohlig(prime, range)
}
if (!(prime instanceof bigInt)) prime = new bigInt(prime)
prime.isubn(1)
let factors = new bigInt(1)
let max = (range || (1 << 12)) + 1
for (let i = 2; i < max; i++)
{
while (!prime.modn(i))
{
prime.idivn(i)
factors.imuln(i)
}
}
return [prime.toString(), factors.toString()]
}
/**
* @private
* @param {Number|String|bigInt} a base
* @param {Number|String|bigInt} b exponent
* @param {Number|String|bigInt} c modulus
* @returns {bigInt} result
*/
function modPow(a, b, c)
{
if (!(a instanceof bigInt)) a = new bigInt(a)
if (!(b instanceof bigInt)) b = new bigInt(b)
if (!(c instanceof bigInt)) c = new bigInt(c)
/* istanbul ignore if */
if (typeof BigInt !== "undefined")
{
function pow(a, b, c)
{
let one = BigInt(1)
let res = BigInt(1)
a = a % c;
while (b > 0)
{
if (b & one)
res = (res * a) % c
b = b >> one
a = (a * a) % c
}
return res
}
a = BigInt(a.toString())
b = BigInt(b.toString())
c = BigInt(c.toString())
return new bigInt(pow(a, b, c).toString())
}
else
{
let red = bigInt.red(c)
return a.toRed(red).redPow(b).fromRed()
}
}
/* istanbul ignore next: Browser specific code */
if(typeof window !== "undefined")
{
if(window.crypto)
{
exports.randomBytes = function(n)
{
let arr = new Uint8Array(n)
window.crypto.getRandomValues(arr)
return arr
}
}
else
{
let warning = false
exports.randomBytes = function(n)
{
if (!warning)
{
console.warn('Window.crypto not detected. This might be insecure, please override the exports.randomBytes function with a secure one.')
warning = true
}
let arr = new Uint8Array(n)
for (var i = 0; i < arr.length; i++)
{
arr[i] = (Math.random() * 256) | 0
}
return arr
}
}
global.jsSHA = jsSHA
global.bigInt = bigInt
global.aesjs = aesjs
}
/**
* Converts a byte array to a hex string
* @private
* @param {Array|Buffer|ArrayBuffer|Uint8Array} byteArray
*/
function toHexString(byteArray)
{
var s = '';
byteArray.forEach(byte =>
{
s += ('0' + (byte & 0xFF).toString(16)).slice(-2);
})
return s;
}
/**
* Gets the scrypt value.
* @private
* @param {String|Buffer|Array} key
* @param {String|Buffer|Array} salt
* @param {Number=} N
* @param {Number=} r
* @param {Number=} p
* @param {Number=} len
* @returns {Promise.<Uint8Array|Buffer>} derived key
*/
function scryptPromise(key, salt, N, r, p, len)
{
if (typeof key === "string")
{
key = Buffer.from(key.normalize('NFKC'))
}
if (typeof salt === "string") salt = Buffer.from(salt, 'hex')
N = N || DEFAULT_SCRYPT_CONFIG.N
r = r || DEFAULT_SCRYPT_CONFIG.r
p = p || DEFAULT_SCRYPT_CONFIG.p
len = len || DEFAULT_SCRYPT_CONFIG.len
if(!Number.isInteger(len))
return Promise.reject('Length is not a number.')
return new Promise((resolve, reject) =>
{
scrypt(key, salt,
{
N: N,
r: r,
p: p,
dkLen: len,
interruptStep: exports.SCRYPT_PAUSE,
encoding: 'binary'
}, (key) =>
{
resolve(key)
})
})
}
const PROMISE_TRICK = function()
{
let arg = arguments
return Contact._modifyPromise(this[0].then(contact =>
{
return contact[this[1]].apply(contact, arg)
}))
}
/**
* A stub class that doesn't actually get used, but helps with autocompletion. Allows you to use Contacts asynchronously.
*/
export class ContactPromise
{
/**
*
* @param {Function} data
* @returns {ContactPromise}
*/
then /* istanbul ignore next */ (data)
{
}
/**
*
* @param {Function} err
* @returns {ContactPromise}
*/
catch /* istanbul ignore next */ (err)
{
}
/**
* Signs data using the Contact, using the Schnorr Signature algorithm.
* This is not how DiscreteCrypt (C++) does it,
* but it will be modified to match this approach.
* @param {*} data
* @param {Boolean} bundle
* @returns {ContactPromise}
*/
sign /* istanbul ignore next */ (data, bundle)
{
}
/**
* Sends the data to the recipient, encrypted.
* @param {*} data
* @returns {Promise.<Object>}
*
*/
open /* istanbul ignore next */ (data)
{
}
/**
* Sends the data to the recipient, encrypted.
* @param {Contact} recipient
* @param {*} data
* @returns {Promise.<Object>}
*/
send /* istanbul ignore next */ (recipient, data)
{
}
/**
* Used to export the (safe) JSON for the Contact
* @param {Object} extra
* @returns {ContactPromise}
*
*/
export /* istanbul ignore next */ (extra)
{
}
/**
* Verifies the signed data.
* @param {Object|Promise.<Object>=} data
* @param {Object|Promise.<Object>=} source
* @returns {ContactPromise}
*/
verify /* istanbul ignore next */ (data, source)
{
}
/**
* Returns the public key
* @returns {ContactPromise.<bigInt>}
*/
publicKey /* istanbul ignore next */ ()
{
}
/**
* Returns the private key
* @returns {ContactPromise.<bigInt>}
*/
privateKey /* istanbul ignore next */ ()
{
}
/**
* Computes the private key for a contact from an input key.
* @param {String|Buffer|Array} key
* @returns {ContactPromise}
*/
compute /* istanbul ignore next */ (key)
{
}
/**
*
* @param {*} extra
* @returns {ContactPromise}
*
*/
clean /* istanbul ignore next */ (extra)
{
}
}
/**
* A contact that can be used to send or receive secure messages. Essentially an abstraction of a public/private key.
*/
export class Contact
{
/**
* Returns the public key
* @returns {bigInt} public key
*/
publicKey()
{
if (!this.public)
{
throw "Public key not set."
}
return new bigInt(this.public)
}
/**
* Returns the private key
* @returns {bigInt} private key
*/
privateKey()
{
if (!this.private)
{
throw "Private key not set."
}
return new bigInt(this.private)
}
/**
*
* @param {*} params
* @protected
* @returns {Contact} Contact
*/
setParams(params)
{
this.params = {
prime: params.prime.toString(),
gen: params.gen.toString()
}
return this
}
/**
*
* @param {*} scryptConfig
* @protected
* @returns {Contact} Contact
*/
setScrypt(scryptConfig)
{
this.scryptConfig = scryptConfig
return this
}
/**
* Alias for fromJSON
* @see fromJSON
* @param {String|Object} json
* @param {Boolean} sync Specifies whether this should be synchronous or not
* @returns {ContactPromise|Contact}
*/
static
import(json, sync)
{
return this.fromJSON(json, sync)
}
/**
* Imports the asynchronous version of a DiscreteCrypt Contact
* @private
* @param {String|Object|Promise} json
*/
static _fromJSONAsync(json)
{
if (!(json instanceof Promise))
{
json = Promise.resolve(json)
}
let contact = json.then(json =>
{
return Contact._fromJSONSync(json)
})
return this._modifyPromise(contact)
}
/**
* Imports the synchronous version of a DiscreteCrypt Contact
* @private
* @param {String|Object} json
*/
static _fromJSONSync(json)
{
if (typeof json === "string")
{
json = JSON.parse(json)
}
let contact = new Contact()
for (var prop in json)
{
contact[prop] = json[prop]
}
return contact
}
/**
* Processes a JSON string or object, and converts it into a Contact class.
*
* @param {String|Object|Promise} json
* @param {Boolean=} sync Determines whether this returns a synchronous contact or asynchronous.
* If true, the input must be synchronous.
* @returns {ContactPromise}
*/
static fromJSON(json, sync)
{
if (sync)
{
return Contact._fromJSONSync(json)
}
return Contact._fromJSONAsync(json)
}
/**
* Used to export the (safe) JSON for the Contact for sharing.
* @param {Object} extra
* @returns {Object} An object that is safe to share with others
*/
export (extra)
{
let res = Object.assign(
{}, this)
delete res.private
if (extra)
{
if (extra.params || extra.all)
{
delete res.params
}
if (extra.scryptConfig || extra.all || extra.scrypt)
{
delete res.scryptConfig
}
}
return JSON.stringify(res)
}
/**
* Sends the data to the recipient, encrypted.
* @param {Contact|ContactPromise} recipient
* @param {*} data
* @returns {Promise.<Object>} Encrypted data
*/
send(recipient, data)
{
return exchange(this, recipient, data)
}
/**
* Opens an encrypted payload for the contact.
* @param {*} data
* @returns {Promise.<Object>} Decrypted data
*/
open(data)
{
return open(this, data)
}
/**
* Signs data using the Contact, using the Schnorr Signature algorithm.
*
* This is not how DiscreteCrypt (C++) currently handles signatures,
* but that will be changed.
*
* @param {Object|Promise.<Object>} data
* @param {Boolean} bundle allows you to specify whether the source data should be bundled in or not.
* @returns {Promise.<Object>} Signature
*/
sign(data, bundle)
{
// Performs the Schnorr Signature Algorithm
// constants for the Scrypt step of the signature.
// this computes K deterministically, (similar to what is recommended in DSA)
// in such a way that protects the upper bits of the private key.
// I would've used an HMAC, but I didn't want any information to leak about the private key (by dividing out the hash).
// These values can likely be tweaked quite freely.
const N = 1 << 5
const r = 4
const p = 1
// gets the private key
let priv = this.privateKey()
/*
* the following line will need to be altered based on the hash algorithm.
* please do not forget this. it's the length of the output in bytes.
* ensuring K > (private.length + hash.length) prevents an attacker from learning information about the private key.
*/
const HASH_LENGTH = 32
const len = Math.round((priv.bitLength() / 8) + HASH_LENGTH + 1)
// Allows asynchronous input
if (!(data instanceof Promise))
{
data = Promise.resolve(data)
}
// computes the signature
return data.then(data =>
{
let d = Buffer.from(JSON.stringify(data))
// scrypt is used to create the K value deterministically
return exports.utils.scryptPromise(d, Buffer.from(priv.toString(16), 'hex'), N, r, p, len).then(k_derived =>
{
let K = new bigInt(toHexString(k_derived), 16)
// K is used to generate R
let R = Buffer.from(modPow(this.params.gen, K, this.params.prime).toString(16), 'hex')
// compute a public hash
let hash = new jsSHA('SHA-256', 'ARRAYBUFFER')
hash.update(R)
hash.update(d)
hash = hash.getHash('HEX')
// computes the signature values
let e = new bigInt(hash, 16)
let s = K.sub(priv.mul(e))
let result = { s: s.toString(16), e: e.toString(16) }
if(bundle) result.data = data
return result
})
})
}
/**
* Verifies the signed data.
* @param {Object|Promise.<Object>} data
* @param {Object|Promise.<Object>=} source
* @returns {Promise.<Boolean>}
*/
verify(data, source)
{
if (!(data instanceof Promise))
{
data = Promise.resolve(data)
}
if (!(source instanceof Promise))
{
source = Promise.resolve(source)
}
return Promise.all([data, source]).then(([data, source]) =>
{
if (!data.s || !data.e) return Promise.reject('Signature Not Verified')
let d = Buffer.from(JSON.stringify(source || data.data))
// Gets the e & s bignums
let s = new bigInt(data.s, 16)
let e = new bigInt(data.e, 16)
// computes the values
let gs = modPow(this.params.gen, s, this.params.prime)
let ye = modPow(this.publicKey(), e, this.params.prime)
// multiplies them together to get R (for the hash)
let R = Buffer.from(gs.mul(ye).mod(new bigInt(this.params.prime)).toString(16), 'hex')
// compute the hash
let hash = new jsSHA('SHA-256', 'ARRAYBUFFER')
hash.update(R)
hash.update(d)
hash = hash.getHash('HEX')
// get it as a bigint
let ev = new bigInt(hash, 16)
if(ev.eq(e)) return source || data.data
return Promise.reject('Signature Not Verified')
})
}
/**
*
* @param {*} extra
* @returns {Contact}
*/
clean(extra)
{
delete this.private
/* istanbul ignore else */
if (extra)
{
if (extra.params || extra.all)
{
delete this.params
}
if (extra.scryptConfig || extra.scrypt || extra.all)
{
delete this.scryptConfig
}
}
return this
}
/**
* Converts the object to the fully asynchronous Contact
* @returns {ContactPromise}
*/
async ()
{
return Contact._modifyPromise(Promise.resolve(this))
}
static _modifyPromise(prom)
{
[
'sign',
'open',
'send',
'export',
'verify',
'publicKey',
'privateKey',
'compute',
'clean'
].forEach(func =>
{
prom[func] = PROMISE_TRICK.bind([prom, func])
})
prom['async'] = () => prom
return prom
}
/**
* Computes the private key for a contact from an input key.
* @param {String|Buffer|Array} key
* @returns {ContactPromise}
*/
compute(key)
{
let salt = Buffer.from(this.salt, 'hex')
/* istanbul ignore else : I'm trusting the user input on this one. scrypt will throw an error otherwise */
if (typeof key === "string")
{
key = Buffer.from(key.normalize('NFKC'))
}
let scryptProm = exports.utils.scryptPromise(key, salt, this.scryptConfig.N, this.scryptConfig.r, this.scryptConfig.p, this.scryptConfig.len).then(key =>
{
this.private = new bigInt(toHexString(key), 16).mod(new bigInt(this.params.prime)).toString()
let publicTest = modPow(this.params.gen, this.privateKey(), this.params.prime).toString()
if (this.public !== publicTest) return Promise.reject("Incorrect Key")
return this
})
return Contact._modifyPromise(scryptProm)
}
/**
* Creates a contact from the given key / salt.
*
* @param {String|Buffer|Uint8Array|Array=} key Key, can be passed in as a string or Buffer-like object.
* @param {String|Buffer|Uint8Array|Array=} salt Salt, can be passed in as a hex string or Buffer-like object.
* @param {Object=} scryptConfig Configuration for Scrypt
* @param {Object=} params Discrete Log Parameters
* @returns {ContactPromise}
*/
static create(key, salt, scryptConfig, params)
{
let contact = new Contact()
// if there is no defined scrypt config, and no key, automatically switch to ephemeral scrypt settings.
if(!key && typeof scryptConfig === "undefined") scryptConfig = EPHEMERAL_SCRYPT_CONFIG
scryptConfig = scryptConfig || DEFAULT_SCRYPT_CONFIG
params = params || DEFAULT_PARAMS
function getKeyPair(key, salt)
{
// numbers get converted to a string
if(typeof key === "number")
{
key = key.toString()
}
// strings get converted to buffers (after normalization)
if (typeof key === "string")
{
key = Buffer.from(key.normalize('NFKC'))
}
// empty key or no key get one randomly generated
if (!key || !key.length)
{
key = exports.randomBytes(32)
}
/* istanbul ignore else: not necessary. I'm trusting that it is an array like object. */
if (typeof salt === "string")
salt = Buffer.from(salt, 'hex')
return exports.utils.scryptPromise(key, salt, scryptConfig.N, scryptConfig.r, scryptConfig.p, scryptConfig.len).then(key =>
{
key = new bigInt(toHexString(key), 16)
let pub = modPow(params.gen, key, params.prime)
return [key, pub]
})
}
if (salt)
{
contact.salt = salt
}
else
{
contact.salt = toHexString(exports.randomBytes(16))
}
contact.setScrypt(scryptConfig).setParams(params)
let keyPairPromise = getKeyPair(key, contact.salt).then(([priv, pub]) =>
{
contact.private = priv.toString()
contact.public = pub.toString()
return contact
})
return Contact._modifyPromise(keyPairPromise)
}
}
let remember = {}
function truncate(x, len)
{
if (x.length > len)
{
return x.substring(0, len)
}
return x
}
// Todo: add some sort of cache cleaner for remember, to prevent memory bloat
/**
* Opens an encrypted payload
* @param {Contact|ContactPromise} receiver
* @param {*} data
*/
export function open(receiver, data)
{
if (!(receiver instanceof Promise))
{
receiver = Promise.resolve(receiver)
}
if (!(data instanceof Promise))
{
data = Promise.resolve(data)
}
return Promise.all([receiver, data]).then(([receiver, data]) =>
{
if (!remember[data.public + ',' + receiver.public])
{
remember[data.public + ',' + receiver.public] = modPow(new bigInt(data.public, 16), receiver.privateKey(), receiver.params.prime).toString(16)
}
let dhexchange = remember[data.public + ',' + receiver.public]
return exports.utils.scryptPromise(Buffer.from(dhexchange, 'hex'), data.hmac, receiver.scryptConfig.N, receiver.scryptConfig.r, receiver.scryptConfig.p, 32).then(dhkey =>
{
let ctr = new aesjs.ModeOfOperation.ctr(dhkey, Buffer.from(truncate(data.hmac, 32), 'hex'))
let ekey = ctr.decrypt(aesjs.utils.hex.toBytes(data.key))
let ctr2 = new aesjs.ModeOfOperation.ctr(ekey, Buffer.from(truncate(data.hmac, 32), 'hex'))
let payload = ctr2.decrypt(aesjs.utils.hex.toBytes(data.payload))
let hmac = new jsSHA('SHA-256', 'ARRAYBUFFER')
hmac.setHMACKey(ekey, 'ARRAYBUFFER')
hmac.update(payload)
hmac = hmac.getHMAC('HEX')
if (hmac === data.hmac)
{
payload = aesjs.utils.utf8.fromBytes(payload)
return JSON.parse(payload)
}
else
{
return Promise.reject('Decryption failed.')
}
})
})
}
/**
* Creates an encrypted payload from the sender, to the receiver.
*
* This code assumes both individuals are using the same parameters.
*
* @todo consider adding advanced options, like allowing the embedding of tuned scrypt parameters
* on the exchange key.
*
* The scrypt step is important for keysize derivation, and creates uniqueness between message exchanges,
* but the speed is not as important in this step.
*
* @param {Contact} sender
* @param {Contact} receiver
* @param {*} msg
*/
export function exchange(sender, receiver, msg)
{
if (!(sender instanceof Promise))
{
sender = Promise.resolve(sender)
}
if (!(receiver instanceof Promise))
{
receiver = Promise.resolve(receiver)
}
if (!(msg instanceof Promise))
{
msg = Promise.resolve(msg)
}
return Promise.all([sender, receiver, msg]).then(([sender, receiver, msg]) =>
{
if (!remember[sender.public + ',' + receiver.public])
{
remember[sender.public + ',' + receiver.public] = modPow(receiver.publicKey(), sender.privateKey(), sender.params.prime).toString(16)
}
msg = JSON.stringify(msg)
let dhexchange = remember[sender.public + ',' + receiver.public]
let key = exports.randomBytes(32)
msg = aesjs.utils.utf8.toBytes(msg)
let hmac = new jsSHA('SHA-256', 'ARRAYBUFFER')
hmac.setHMACKey(key, 'ARRAYBUFFER')
hmac.update(msg)
hmac = hmac.getHMAC('HEX')
return exports.utils.scryptPromise(Buffer.from(dhexchange, 'hex'), hmac, receiver.scryptConfig.N, receiver.scryptConfig.r, receiver.scryptConfig.p, 32).then(dhkey =>
{
let ctr = new aesjs.ModeOfOperation.ctr(dhkey, Buffer.from(truncate(hmac, 32), 'hex'))
let ekey = ctr.encrypt(key)
ekey = aesjs.utils.hex.fromBytes(ekey)
let ctr2 = new aesjs.ModeOfOperation.ctr(key, Buffer.from(truncate(hmac, 32), 'hex'))
let payload = ctr2.encrypt(msg)
payload = aesjs.utils.hex.fromBytes(payload)
return {
payload: payload,
key: ekey,
hmac: hmac,
public: sender.publicKey().toString(16)
}
})
})
}
/**
* DiscreteCrypt Symmetric Utilities
* @hideconstructor
*/
export class Symmetric
{
/**
* Uses the Authenticated Encryption Mechanism from the DiscreteCrypt Protocol to symmetrically encrypt the data
* using a given input key.
*
* Uses the input key rather than a DH Exchange.
*
* @param {String|Buffer|Array} inputKey
* @param {*} msg
* @param {Object=} options
*/
static encrypt(inputKey, msg, options)
{
if (!options) options = {}
if (!options.scrypt) options.scrypt = DEFAULT_SCRYPT_CONFIG
if (typeof inputKey === "undefined")
{
return Promise.reject('No input key provided.')
}
/* istanbul ignore else */
if (typeof inputKey === "string")
{
inputKey = Buffer.from(inputKey.normalize('NFKC'))
}
if (inputKey.length === 0)
{
return Promise.reject('Input key empty.')
}
let key = exports.randomBytes(32)
if(!options.raw)
{
msg = aesjs.utils.utf8.toBytes(JSON.stringify(msg))
}
let hmac = new jsSHA('SHA-256', 'ARRAYBUFFER')
hmac.setHMACKey(key, 'ARRAYBUFFER')
hmac.update(msg)
hmac = hmac.getHMAC('HEX')
return exports.utils.scryptPromise(inputKey, hmac, options.scrypt.N, options.scrypt.r, options.scrypt.p, 32).then(dhkey =>
{
let ctr = new aesjs.ModeOfOperation.ctr(dhkey, Buffer.from(truncate(hmac, 32), 'hex'))
let ekey = ctr.encrypt(key)
ekey = aesjs.utils.hex.fromBytes(ekey)
let ctr2 = new aesjs.ModeOfOperation.ctr(key, Buffer.from(truncate(hmac, 32), 'hex'))
let payload = ctr2.encrypt(msg)
payload = aesjs.utils.hex.fromBytes(payload)
return {
payload: payload,
key: ekey,
hmac: hmac
}
})
}
/**
* Uses the Authenticated Encryption Mechanism from the DiscreteCrypt Protocol to symmetrically encrypt the data
* using a given input key.
*
* Uses the input key rather than a DH Exchange.
*
* @param {String|Buffer|Array} inputKey
* @param {Object} data
* @param {Object=} options
*/
static decrypt(inputKey, data, options)
{
if (!options) options = {}
if (!options.scrypt) options.scrypt = DEFAULT_SCRYPT_CONFIG
if (typeof inputKey === "undefined")
{
return Promise.reject('No input key provided.')
}
/* istanbul ignore else */
if (typeof inputKey === "string")
{
inputKey = Buffer.from(inputKey.normalize('NFKC'))
}
if (inputKey.length === 0)
{
return Promise.reject('Input key empty.')
}
return exports.utils.scryptPromise(inputKey, data.hmac, options.scrypt.N, options.scrypt.r, options.scrypt.p, 32).then(ikey =>
{
let ctr = new aesjs.ModeOfOperation.ctr(ikey, Buffer.from(truncate(data.hmac, 32), 'hex'))
let ekey = ctr.decrypt(aesjs.utils.hex.toBytes(data.key))
let ctr2 = new aesjs.ModeOfOperation.ctr(ekey, Buffer.from(truncate(data.hmac, 32), 'hex'))
let payload = ctr2.decrypt(aesjs.utils.hex.toBytes(data.payload))
let hmac = new jsSHA('SHA-256', 'ARRAYBUFFER')
hmac.setHMACKey(ekey, 'ARRAYBUFFER')
hmac.update(payload)
hmac = hmac.getHMAC('HEX')
if (hmac === data.hmac)
{
if(!options.raw)
{
payload = aesjs.utils.utf8.fromBytes(payload)
return JSON.parse(payload)
}
else
{
return payload
}
}
else
{
return Promise.reject('Decryption failed.')
}
})
}
}
/**
* Provides sane defaults for use in DiscreteCrypt.js applications
*/
export class defaults
{
/**
* Returns the default parameters of DiscreteCrypt.js.
* @returns {Object}
*/
static params()
{
return Object.freeze(DEFAULT_PARAMS)
}
/**
*
* This Scrypt configuration is the default recommended scrypt configuration.
* This is for securing highly sensitive data in worst case conditions.
* @returns {Object}
*/
static scrypt()
{
return Object.freeze(DEFAULT_SCRYPT_CONFIG)
}
/**
* This Scrypt Configuration should be applied when the keys are ephemeral.
* @returns {Object}
*
*/
static ephemeralScrypt()
{
return Object.freeze(EPHEMERAL_SCRYPT_CONFIG)
}
/**
*
* Should be applied for performance reasons when the author is willing to accept
* slightly reduced security for performance reasons.
*
* This can securely be applied in situations where password input is reasonably decent.
*
* Consider it a healthy middle ground between the default (top-secret) and
* ephemeral.
* @returns {Object}
*/
static tunedScrypt()
{
return Object.freeze(TUNED_SCRYPT_CONFIG)
}
}
exports.utils = {
modPow: modPow,
truncate: truncate,
scryptPromise: scryptPromise,
hex: toHexString,
pohlig: pohlig
}
exports.defaults = defaults
exports.Symmetric = Symmetric
exports.clearCache = function()
{
remember = {}
}
exports.Contact = Contact
exports.open = open
exports.exchange = exchange
exports.SCRYPT_PAUSE = 0