TFS_ANTIGUO/SIGPC/hfda/scripts/random.js
kledezma 290b81b54d SIGPC
git-tfs-id: [http://192.168.1.10:8080/tfs/AyA]$/SINORT;C518
2021-01-22 14:59:35 +00:00

207 lines
No EOL
9.3 KiB
JavaScript

//*******************************************************************************
//
// 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 */
/// <reference path="utilities.js" />
/// <reference path="aes.js" />
/// <dictionary>msrcrypto, utils, xor, res, csrc, nist, nistpubs, prng</dictionary>
/// #endregion JSCop/JsHint
/* @constructor */ function MsrcryptoPrng() {
/// <summary>Pseudo Random Number Generator function/class.</summary>
/// <remarks>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.
/// </remarks>
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) {
/// <summary>Adds one to a big integer represented in an array (the first argument).</summary>
/// <param name="counter" counter="Array">The counter byte array to add one to encoded in big endian; index 0 is the MSW.</param>
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() {
/// <summary>Instantiate the PRNG with given entropy and personalization string.</summary>
/// <param name="entropy" type="Array">Array of bytes obtained from the source of entropy input.</param>
/// <param name="personalizationString" type="Array">Optional application-provided personalization string.</param>
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) {
/// <summary>Reseed the PRNG with additional entropy.</summary>
/// <param name="entropy" type="Array">Input entropy.</param>
/// <param name="additionalEntropy" type="Array">Optional additional entropy input.</param>
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) {
/// <summary>Add the providedData to the internal entropy pool, and update internal state.</summary>
/// <param name="providedData" type="Array">Input to add to the internal entropy pool.</param>
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) {
/// <summary>Generate pseudo-random bits, and update the internal PRNG state.</summary>
/// <param name="requestedBytes" type="Number">Number of pseudorandom bytes to be returned.</param>
/// <param name="additionalInput" type="Array">Application-provided additional input array (optional).</param>
/// <returns>Generated pseudorandom bytes.</returns>
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,
/// <summary>Reseed the PRNG with additional entropy.</summary>
/// <param name="entropy" type="Array">Input entropy.</param>
/// <param name="additionalEntropy" type="Array">Optional additional entropy input.</param>
init: function (entropy,/*@optional*/ personalization) {
/// <summary>Initialize the PRNG by seeing with entropy and optional input data.</summary>
/// <param name="entropy" type="Array">Input entropy.</param>
/// <param name="personalization" type="Array">Optional input.</param>
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.
/// <disable>JS3085.VariableDeclaredMultipleTimes</disable>
var msrcryptoPseudoRandom = new MsrcryptoPrng();
/// <enable>JS3085.VariableDeclaredMultipleTimes</enable>