//******************************************************************************* // // Copyright 2014 Microsoft // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //******************************************************************************* /// #region JSCop/JsHint /* global msrcryptoUtilities */ /* jshint -W016 */ /// /// /// msrcrypto, utils, xor, res, csrc, nist, nistpubs, prng /// #endregion JSCop/JsHint /* @constructor */ function MsrcryptoPrng() { /// Pseudo Random Number Generator function/class. /// This is the PRNG engine, not the entropy collector. /// The engine must be initialized with adequate entropy in order to generate cryptographically secure /// random numbers. It is hard to get entropy, but see the entropy functoin/class for the entropy gatherer. /// This is not an object instantiation, but the definition of the object. The actual /// object must be instantiated somewhere else as needed. /// if (!(this instanceof MsrcryptoPrng)) { throw new Error("create MsrcryptoPrng object with new keyword"); } // Fallback for browsers which do not implements crypto API yet // implementation of http://csrc.nist.gov/publications/nistpubs/800-90A/SP800-90A.pdf. // Use AES-256 in CTR mode of operation as defined in Section 10.2.1. var initialized = false; // Internal state definitions are as follows. // v : internal variable that will ultimately be the random output // key: the AES key (256 bits) // keyLen: the AES key length in bytes // reseedCounter: the number of requests for pseudorandom bits since instantiation/reseeding // reseedInterval: Maximum number of generate calls per seed or reseed. SP800-90A says 2^48 for AES, we use 2^24. var key; var v; var keyLen; var seedLen; var reseedCounter = 1; var reseedInterval = 1 << 24; // Initialize this instance (constructor like function) initialize(); function addOne(counter) { /// Adds one to a big integer represented in an array (the first argument). /// The counter byte array to add one to encoded in big endian; index 0 is the MSW. var i; for (i = counter.length - 1; i >= 0; i -= 1) { counter[i] += 1; if (counter[i] >= 256) { counter[i] = 0; } if (counter[i]) { break; } } } function initialize() { /// Instantiate the PRNG with given entropy and personalization string. /// Array of bytes obtained from the source of entropy input. /// Optional application-provided personalization string. key = msrcryptoUtilities.getVector(32); v = msrcryptoUtilities.getVector(16); // AES block length keyLen = 32; seedLen = 48; // From SP800-90A, section 10.2.1 as of 2014. reseedCounter = 1; } function reseed(entropy,/*@optional*/ additionalEntropy) { /// Reseed the PRNG with additional entropy. /// Input entropy. /// Optional additional entropy input. additionalEntropy = additionalEntropy || [0]; if (additionalEntropy.length > seedLen) { throw new Error("Incorrect entropy or additionalEntropy length"); } additionalEntropy = additionalEntropy.concat(msrcryptoUtilities.getVector(seedLen - additionalEntropy.length)); // Process the entropy input in blocks with the same additional entropy. // This is equivalent to the caller chunking entropy in blocks and calling this function for each chunk. entropy = entropy.concat(msrcryptoUtilities.getVector((seedLen - (entropy.length % seedLen)) % seedLen)); for (var i = 0; i < entropy.length; i += seedLen) { var seedMaterial = msrcryptoUtilities.xorVectors(entropy.slice(i, i + seedLen), additionalEntropy); update(seedMaterial); } reseedCounter = 1; } function update(providedData) { /// Add the providedData to the internal entropy pool, and update internal state. /// Input to add to the internal entropy pool. var temp = []; var blockCipher = new msrcryptoBlockCipher.aes(key); while (temp.length < seedLen) { addOne(v); var toEncrypt = v.slice(0, 16); var outputBlock = blockCipher.encrypt(toEncrypt); // AES-256 temp = temp.concat(outputBlock); } temp = msrcryptoUtilities.xorVectors(temp, providedData); key = temp.slice(0, keyLen); v = temp.slice(keyLen); } function generate(requestedBytes,/*@optional*/ additionalInput) { /// Generate pseudo-random bits, and update the internal PRNG state. /// Number of pseudorandom bytes to be returned. /// Application-provided additional input array (optional). /// Generated pseudorandom bytes. if (requestedBytes >= 65536) { throw new Error("too much random requested"); } if (reseedCounter > reseedInterval) { throw new Error("Reseeding is required"); } if (additionalInput && additionalInput.length > 0) { while (additionalInput.length < seedLen) { additionalInput = additionalInput.concat(msrcryptoUtilities.getVector(seedLen - additionalInput.length)); } update(additionalInput); } else { additionalInput = msrcryptoUtilities.getVector(seedLen); } var temp = []; var blockCipher = new msrcryptoBlockCipher.aes(key); while (temp.length < requestedBytes) { addOne(v); var toEncrypt = v.slice(0, v.length); var outputBlock = blockCipher.encrypt(toEncrypt); temp = temp.concat(outputBlock); } temp = temp.slice(0, requestedBytes); update(additionalInput); reseedCounter += 1; return temp; } return { reseed: reseed, /// Reseed the PRNG with additional entropy. /// Input entropy. /// Optional additional entropy input. init: function (entropy,/*@optional*/ personalization) { /// Initialize the PRNG by seeing with entropy and optional input data. /// Input entropy. /// Optional input. if (entropy.length < seedLen) { throw new Error("Initial entropy length too short"); } initialize(); reseed(entropy, personalization); initialized = true; }, getBytes: function (length, /*@optional*/ additionalInput) { if (!initialized) { throw new Error("can't get randomness before initialization"); } return generate(length, /*@optional*/ additionalInput); }, getNonZeroBytes: function (length, additionalInput) { if (!initialized) { throw new Error("can't get randomness before initialization"); } var result = [], buff; while (result.length < length) { buff = generate(length, additionalInput); for (var i = 0 ; i < buff.length; i += 1) { if (buff[i] !== 0) { result.push(buff[i]); } } } return result.slice(0, length); } }; } // This is the PRNG object per instantiation, including one per worker. // The instance in the main thread is used to seed the instances in workers. // TODO: Consider combining the entropy pool in the main thread with the PRNG instance in the main thread. /// JS3085.VariableDeclaredMultipleTimes var msrcryptoPseudoRandom = new MsrcryptoPrng(); /// JS3085.VariableDeclaredMultipleTimes