| 'use strict'; |
| |
| var hash = require('hash.js'); |
| var utils = require('minimalistic-crypto-utils'); |
| var assert = require('minimalistic-assert'); |
| |
| function HmacDRBG(options) { |
| if (!(this instanceof HmacDRBG)) |
| return new HmacDRBG(options); |
| this.hash = options.hash; |
| this.predResist = !!options.predResist; |
| |
| this.outLen = this.hash.outSize; |
| this.minEntropy = options.minEntropy || this.hash.hmacStrength; |
| |
| this._reseed = null; |
| this.reseedInterval = null; |
| this.K = null; |
| this.V = null; |
| |
| var entropy = utils.toArray(options.entropy, options.entropyEnc || 'hex'); |
| var nonce = utils.toArray(options.nonce, options.nonceEnc || 'hex'); |
| var pers = utils.toArray(options.pers, options.persEnc || 'hex'); |
| assert(entropy.length >= (this.minEntropy / 8), |
| 'Not enough entropy. Minimum is: ' + this.minEntropy + ' bits'); |
| this._init(entropy, nonce, pers); |
| } |
| module.exports = HmacDRBG; |
| |
| HmacDRBG.prototype._init = function init(entropy, nonce, pers) { |
| var seed = entropy.concat(nonce).concat(pers); |
| |
| this.K = new Array(this.outLen / 8); |
| this.V = new Array(this.outLen / 8); |
| for (var i = 0; i < this.V.length; i++) { |
| this.K[i] = 0x00; |
| this.V[i] = 0x01; |
| } |
| |
| this._update(seed); |
| this._reseed = 1; |
| this.reseedInterval = 0x1000000000000; // 2^48 |
| }; |
| |
| HmacDRBG.prototype._hmac = function hmac() { |
| return new hash.hmac(this.hash, this.K); |
| }; |
| |
| HmacDRBG.prototype._update = function update(seed) { |
| var kmac = this._hmac() |
| .update(this.V) |
| .update([ 0x00 ]); |
| if (seed) |
| kmac = kmac.update(seed); |
| this.K = kmac.digest(); |
| this.V = this._hmac().update(this.V).digest(); |
| if (!seed) |
| return; |
| |
| this.K = this._hmac() |
| .update(this.V) |
| .update([ 0x01 ]) |
| .update(seed) |
| .digest(); |
| this.V = this._hmac().update(this.V).digest(); |
| }; |
| |
| HmacDRBG.prototype.reseed = function reseed(entropy, entropyEnc, add, addEnc) { |
| // Optional entropy enc |
| if (typeof entropyEnc !== 'string') { |
| addEnc = add; |
| add = entropyEnc; |
| entropyEnc = null; |
| } |
| |
| entropy = utils.toArray(entropy, entropyEnc); |
| add = utils.toArray(add, addEnc); |
| |
| assert(entropy.length >= (this.minEntropy / 8), |
| 'Not enough entropy. Minimum is: ' + this.minEntropy + ' bits'); |
| |
| this._update(entropy.concat(add || [])); |
| this._reseed = 1; |
| }; |
| |
| HmacDRBG.prototype.generate = function generate(len, enc, add, addEnc) { |
| if (this._reseed > this.reseedInterval) |
| throw new Error('Reseed is required'); |
| |
| // Optional encoding |
| if (typeof enc !== 'string') { |
| addEnc = add; |
| add = enc; |
| enc = null; |
| } |
| |
| // Optional additional data |
| if (add) { |
| add = utils.toArray(add, addEnc || 'hex'); |
| this._update(add); |
| } |
| |
| var temp = []; |
| while (temp.length < len) { |
| this.V = this._hmac().update(this.V).digest(); |
| temp = temp.concat(this.V); |
| } |
| |
| var res = temp.slice(0, len); |
| this._update(add); |
| this._reseed++; |
| return utils.encode(res, enc); |
| }; |