Comparing version 1.0.0 to 1.0.1
182
index.js
@@ -0,1 +1,3 @@ | ||
(function(root) { | ||
/** | ||
@@ -26,3 +28,3 @@ * Cryptographically strong, hat-compatible, pseudo-random number generator | ||
*/ | ||
var randomBufferSize = 1024; | ||
var randomBufferSize = 4096; | ||
/** | ||
@@ -43,4 +45,37 @@ * The first unused byte in the random data buffer. | ||
if (typeof(process) === "object" && typeof(process.versions) === "object" && | ||
typeof(process.versions["node"]) === "string") { | ||
/** | ||
* Generates a random identifier. | ||
* | ||
* This implements hat's API. Take a look at {@link crypto.numberGenerator} for | ||
* a higher-performance API. | ||
* | ||
* @param {number} bits the desired number of bits of randomness | ||
* @param {number} base the base/radix used to represent the random number; | ||
* must be between 2 and 36 | ||
* @returns {string} a randomly generated identifier that meets the constraints | ||
* above; the identifiers generated for a given base and number of bits are | ||
* guaranteed to have the same length; in order to satisfy this constraint, | ||
* the returned identifier might have leading zeros | ||
* @alias cryptohat | ||
*/ | ||
var cryptohat = function(bits, base) { | ||
bits = bits || 128; | ||
if (!base && base !== 0) | ||
base = 16; | ||
return cryptohat.generator(bits, base)(); | ||
}; | ||
if (typeof(module) !== "undefined" && "exports" in module) { | ||
// Common.js environment. | ||
module.exports = cryptohat; | ||
} else { | ||
// Browser. | ||
if (typeof(global) !== "undefined") | ||
root = global; | ||
root.cryptohat = cryptohat; | ||
} | ||
if (typeof(process) !== "undefined" && | ||
typeof((process.versions || {}).node) === "string") { | ||
// Node.js implementation based on crypto.randomBytes() | ||
@@ -57,15 +92,25 @@ var crypto = require("crypto"); | ||
}; | ||
} else if (typeof(window.crypto) === "object" && | ||
typeof(window.crypto.getRandomBytes) === "function") { | ||
// Modern browser implementation based on window.crypto.getRandomValues() | ||
randomBuffer = new Uint32Array(randomBufferSize / 4); | ||
// NOTE: This is exported for manual testing. | ||
cryptohat._engine = "crypto.randomBytes()"; | ||
} else if (typeof(((root || {}).crypto || {}).getRandomValues) === | ||
"function") { | ||
// Modern browser implementation based on the W3C Crypto API | ||
// NOTE: Using the root object instead of window lets us find the Crypto API | ||
// in Web workers. | ||
randomBufferSize /= 4; | ||
randomBuffer = new Uint32Array(randomBufferSize); | ||
randomBufferOffset = randomBufferSize; | ||
random32 = function() { | ||
if (randomBufferOffset === randomBufferSize) { | ||
randomBufferOffset = 0; | ||
window.crypto.getRandomValues(randomBuffer); | ||
root.crypto.getRandomValues(randomBuffer); | ||
} | ||
var returnValue = randomBuffer.readUInt32LE(randomBufferOffset); | ||
randomBufferOffset += 4; | ||
return randomBuffer[randomBufferOffset++]; | ||
var returnValue = randomBuffer[randomBufferOffset]; | ||
randomBufferOffset += 1; | ||
return returnValue; | ||
}; | ||
// NOTE: This is exported for manual testing. | ||
cryptohat._engine = "window.crypto.getRandomValues()"; | ||
} else { | ||
@@ -80,28 +125,10 @@ // Compatibility implementation for old browsers. | ||
// better chance to implement the multiplication as << 32. | ||
return Math.random() * 4294967296; | ||
return Math.floor(Math.random() * 4294967296); | ||
}; | ||
// NOTE: This is exported for manual testing. | ||
cryptohat._engine = "Math.random()"; | ||
} | ||
/** | ||
* Generates a random identifier. | ||
* | ||
* This implements hat's API. Take a look at {@link crypto.numberGenerator} for | ||
* a higher-performance API. | ||
* | ||
* @param {number} bits the desired number of bits of randomness | ||
* @param {number} base the base/radix used to represent the random number; | ||
* must be between 2 and 36 | ||
* @returns {string} a randomly generated identifier that meets the constraints | ||
* above; the identifiers generated for a given base and number of bits are | ||
* guaranteed to have the same length; in order to satisfy this constraint, | ||
* the returned identifier might have leading zeros | ||
*/ | ||
module.exports = function(bits, base) { | ||
bits = bits || 128; | ||
if (!base && base !== 0) | ||
base = 16; | ||
return module.exports.generator(bits, base)(); | ||
}; | ||
/** | ||
* Cache for the generator functions produced by this module. | ||
@@ -132,3 +159,3 @@ * | ||
*/ | ||
module.exports.generator = function(bits, base) { | ||
cryptohat.generator = function(bits, base) { | ||
var cacheKey = (base) ? bits.toString() + "." + base.toString() : | ||
@@ -240,3 +267,3 @@ bits.toString(); | ||
// NOTE: This is exported for testing. | ||
module.exports._array32ToString = array32ToString; | ||
cryptohat._array32ToString = array32ToString; | ||
@@ -246,19 +273,19 @@ /** | ||
* | ||
* @param {string} string the string to be repeated | ||
* @param {number} count the number of times to repeat the string | ||
* @return {string} "string", repeated "count" times | ||
* @return {string} a string consisting of count zero ("0") characters | ||
* @private | ||
*/ | ||
var stringRepeat = null; | ||
var zeroRepeat = null; | ||
if (typeof(String.prototype.repeat) === "function") { | ||
// Fast path for node.js >= 4 and modern browsers. | ||
stringRepeat = function(string, count) { | ||
return string.repeat(count); | ||
zeroRepeat = function(count) { | ||
return "0".repeat(count); | ||
}; | ||
} else { | ||
// Slow path for node.js <= 0.12 and old browsers. | ||
stringRepeat = function(string, count) { | ||
zeroRepeat = function(count) { | ||
var result = ""; | ||
for (var i = 0; i < count; ++i) | ||
result += string; | ||
result += "0"; | ||
return result; | ||
@@ -268,5 +295,57 @@ }; | ||
// NOTE: This is exported for testing. | ||
module.exports._stringRepeat = stringRepeat; | ||
cryptohat._zeroRepeat = zeroRepeat; | ||
/** | ||
* Pads a string with zeros until it reaches a desired length. | ||
* | ||
* @param {string} string the string to be padded | ||
* @param {number} the desired string length | ||
* @return {string} a string that has at least the desired length; the returned | ||
* string will consist of some zero ("0") characters, followed by the given | ||
* string | ||
* @private | ||
*/ | ||
var zeroPad = function(string, length) { | ||
var digitsNeeded = length - string.length; | ||
if (digitsNeeded > 0) | ||
string = zeroRepeat(digitsNeeded) + string; | ||
return string; | ||
}; | ||
// NOTE: This is exported for testing. | ||
cryptohat._zeroPad = zeroPad; | ||
/** | ||
* Special case of {@link array32ToString} when base=16. | ||
* | ||
* @see array32ToString | ||
* @param {Array<number>} array a big-endian representation of the number; each | ||
* element in the array is a 32-bit digit; the elements in the array will be | ||
* trashed | ||
* @param {number} base the base/radix used to represent the number | ||
* @param {Array<string>} digits buffer used to store an intermediate | ||
* representation of the number; the elements in the array will be trashed | ||
* @return {string} the textual representation of the number | ||
* @private | ||
*/ | ||
var array32ToHexString = function(array, base, digits) { | ||
// NOTE: The digits array will generally be large enough to contain | ||
// everything except for possibly the first few digits in the first | ||
// array element. | ||
var string = ""; | ||
for (var i = 0; i < array.length; ++i) { | ||
// NOTE: Each 32-bit character expands to 8 hexadecimal digits. | ||
string += zeroPad(array[i].toString(16), 8); | ||
} | ||
var extraCharacters = string.length - digits.length; | ||
if (extraCharacters > 0) | ||
string = string.substring(extraCharacters); | ||
return string; | ||
}; | ||
// NOTE: This is exported for testing. | ||
cryptohat._array32ToHexString = array32ToHexString; | ||
/** | ||
* Creates a random identifier generator. | ||
@@ -290,10 +369,6 @@ * | ||
// Fast path where we can use JavaScript's toString(). | ||
var numberGenerator = module.exports.generator(bits, 0); | ||
var numberGenerator = cryptohat.generator(bits, 0); | ||
return function() { | ||
var identifier = numberGenerator().toString(base); | ||
var missingDigits = digitCount - identifier.length; | ||
if (missingDigits > 0) | ||
identifier = stringRepeat("0", missingDigits) + identifier; | ||
return identifier; | ||
} | ||
return zeroPad(numberGenerator().toString(base), digitCount); | ||
}; | ||
} | ||
@@ -309,2 +384,3 @@ | ||
numbers[i] = 0; | ||
var stringifer = (base === 16) ? array32ToHexString : array32ToString; | ||
if (bits % 32 === 0) { | ||
@@ -315,3 +391,3 @@ return function() { | ||
return array32ToString(numbers, base, digits); | ||
return stringifer(numbers, base, digits); | ||
}; | ||
@@ -325,5 +401,7 @@ } else { | ||
return array32ToString(numbers, base, digits); | ||
return stringifer(numbers, base, digits); | ||
}; | ||
} | ||
}; | ||
})(this); |
{ | ||
"name": "cryptohat", | ||
"version": "1.0.0", | ||
"version": "1.0.1", | ||
"description": "Cryptographically strong, hat-compatible, pseudo-random number generator", | ||
@@ -22,5 +22,7 @@ "keywords": ["hat", "crypto", "rng", "random"], | ||
"devDependencies": { | ||
"bower": ">= 0", | ||
"chai": ">= 3.5.0", | ||
"coveralls": ">= 2.11.8", | ||
"hat": ">= 0.0.3", | ||
"http-server": ">= 0", | ||
"ink-docstrap": ">= 1.1.4", | ||
@@ -32,4 +34,2 @@ "istanbul": ">= 0.4.2", | ||
"mocha-lcov-reporter": ">= 1.2.0", | ||
"sinon": ">= 1.17.3", | ||
"sinon-chai": ">= 2.8.0", | ||
"watch": ">= 0.16.0" | ||
@@ -36,0 +36,0 @@ }, |
@@ -1,2 +0,2 @@ | ||
# Hat-Compatible CSPRNG | ||
# `hat`-Compatible CSPRNG | ||
@@ -12,7 +12,7 @@ [![Build Status](https://travis-ci.org/heap/cryptohat.svg?branch=master)](https://travis-ci.org/heap/cryptohat) | ||
to generate the random identifiers. This is especially beneficial when using | ||
older versions of [V8](https://en.wikipedia.org/wiki/V8_(JavaScript_engine) | ||
older versions of [V8](https://en.wikipedia.org/wiki/V8_(JavaScript_engine)) | ||
that exhibit [this bug](https://bugs.chromium.org/p/v8/issues/detail?id=4566). | ||
In our use cases, `cryptohat` takes up to 3x more time to produce a random | ||
identifier than `hat`. `cryptohat` also exposes an alternative API that | ||
string than `hat`. However, `cryptohat` also exposes an alternative API that | ||
produces random *numbers* up to 2x faster than `hat`, while still using a | ||
@@ -24,12 +24,19 @@ CSPRNG. | ||
This package is tested on node.js 0.10 and above. Browser support is planned | ||
for a future version. | ||
This package should work on any reasonably modern browser or node.js version. | ||
Every commit is tested using [continuous integration](https://travis-ci.org/) | ||
on node.js 0.10 and above. Releases are also tested against the most recent | ||
versions of [Chrome](https://www.google.com/chrome/), | ||
[Firefox](https://www.mozilla.org/firefox/), | ||
[Safari](http://www.apple.com/safari/), and | ||
[Internet Explorer](http://windows.microsoft.com/internet-explorer/). | ||
## Installation | ||
Install using [npm](https://www.npmjs.com/). | ||
Install using [npm](https://www.npmjs.com/) or [bower](http://bower.io/). | ||
```bash | ||
npm install cryptohat@0.1.x --save | ||
npm install cryptohat@1.x --save | ||
bower install cryptohat@1.x --save | ||
``` | ||
@@ -89,2 +96,3 @@ | ||
npm install | ||
node node_modules/.bin/bower install | ||
``` | ||
@@ -113,2 +121,20 @@ | ||
When modifying code around or inside feature detection blocks (combinations of | ||
`if` and `typeof`), make sure the tests pass at least in Chrome and Firefox, by | ||
opening [test/index.html](./test/index.html) in the browsers. | ||
```bash | ||
open test/index.html # On OSX. | ||
xdg-open test/index.html # On Linux. | ||
``` | ||
When testing against a browser in a VM (e.g., for Internet Explorer), spawn a | ||
local Web server inside the source tree and visit it inside the VM | ||
(e.g., `http://10.0.2.2:8080/test/index.html`). | ||
```bash | ||
node node_modules/.bin/http-server | ||
``` | ||
If you submit a | ||
@@ -115,0 +141,0 @@ [pull request](https://help.github.com/articles/using-pull-requests/), |
@@ -1,14 +0,33 @@ | ||
var cryptohat = require(".."); | ||
var hat = require("hat"); | ||
(function() { | ||
var cryptohat = __test.cryptohat; | ||
var hat = __test.hat; | ||
// Runs the target function for a number of iterations and returns the number | ||
// of nanoseconds passed per iteration. | ||
var benchmark = function (iterations, code) { | ||
var t0 = process.hrtime(); | ||
for (var i = 0; i < iterations; ++i) { | ||
code(); | ||
var benchmark = null; | ||
if (typeof(process) !== "undefined" && typeof(process.hrtime) === "function") { | ||
// Node.js implementation based on process.hrtime() | ||
benchmark = function(iterations, code) { | ||
var t0 = process.hrtime(); | ||
for (var i = 0; i < iterations; ++i) { | ||
code(); | ||
} | ||
t1 = process.hrtime(); | ||
return ((t1[0] - t0[0]) * 1e9 + t1[1] - t1[0]) / iterations; | ||
} | ||
t1 = process.hrtime(); | ||
} else if(typeof(performance) !== "undefined" && | ||
typeof(performance.now) === "function") { | ||
// Browser implementation based on performance.now() | ||
benchmark = function(iterations, code) { | ||
var t0 = performance.now(); | ||
for (var i = 0; i < iterations; ++i) { | ||
code(); | ||
} | ||
t1 = performance.now(); | ||
return ((t1[0] - t0[0]) * 1e9 + t1[1] - t1[0]) / iterations; | ||
return Math.ceil((t1 - t0) * 1e6); | ||
} | ||
} | ||
@@ -59,3 +78,13 @@ | ||
}); | ||
it("is at most 2x slower than hat for base-16 128-bits", function() { | ||
var baseline = benchmark(iterations, function() { | ||
return hat(128, 16); | ||
}); | ||
var us = benchmark(iterations, cryptohat.generator(128, 16)); | ||
console.log([baseline / us, "baseline", baseline, "us", us]); | ||
expect(baseline / us).to.be.at.least(0.2); | ||
}); | ||
}); | ||
}); | ||
})(); |
@@ -1,3 +0,4 @@ | ||
var cryptohat = require(".."); | ||
(function() { | ||
var cryptohat = __test.cryptohat; | ||
var generator = cryptohat.generator; | ||
@@ -31,1 +32,3 @@ | ||
}); | ||
})(); |
@@ -1,6 +0,22 @@ | ||
global.chai = require("chai"); | ||
global.sinon = require("sinon"); | ||
global.chai.use(require("sinon-chai")); | ||
(function(root) { | ||
global.assert = global.chai.assert; | ||
global.expect = global.chai.expect; | ||
if (typeof(global) !== "undefined") | ||
root = global; | ||
root.__test = {}; | ||
if (typeof(require) === "function") { | ||
root.__test.cryptohat = require(".."); | ||
root.chai = require("chai"); | ||
root.__test.hat = require("hat"); | ||
root.__test.lodash = require("lodash"); | ||
} else { | ||
root.__test.cryptohat = root.cryptohat; | ||
root.__test.hat = root.hat; | ||
root.__test.lodash = root._; | ||
} | ||
root.assert = root.chai.assert; | ||
root.expect = root.chai.expect; | ||
})(this); |
@@ -1,4 +0,6 @@ | ||
var _ = require("lodash"); | ||
var cryptohat = require(".."); | ||
(function() { | ||
var _ = __test.lodash; | ||
var cryptohat = __test.cryptohat; | ||
var testNumberRng = function(generator, bits, count) { | ||
@@ -187,1 +189,3 @@ var numbers = []; | ||
}); | ||
})(); |
@@ -1,21 +0,52 @@ | ||
var cryptohat = require(".."); | ||
(function() { | ||
var cryptohat = __test.cryptohat; | ||
var array32ToString = cryptohat._array32ToString; | ||
var stringRepeat = cryptohat._stringRepeat; | ||
var array32ToHexString = cryptohat._array32ToHexString; | ||
var zeroPad = cryptohat._zeroPad; | ||
var zeroRepeat = cryptohat._zeroRepeat; | ||
describe("cryptohat._stringRepeat", function() { | ||
describe("cryptohat._zeroRepeat", function() { | ||
it("works for count 0", function() { | ||
expect(stringRepeat("a", 0)).to.equal(""); | ||
expect(zeroRepeat(0)).to.equal(""); | ||
}); | ||
it("works for count 1", function() { | ||
expect(stringRepeat("a", 1)).to.equal("a"); | ||
expect(zeroRepeat(1)).to.equal("0"); | ||
}); | ||
it("works for count 2", function() { | ||
expect(stringRepeat("a", 2)).to.equal("aa"); | ||
expect(zeroRepeat(2)).to.equal("00"); | ||
}); | ||
it("works for count 10", function() { | ||
expect(stringRepeat("a", 10)).to.equal("aaaaaaaaaa"); | ||
expect(zeroRepeat(10)).to.equal("0000000000"); | ||
}); | ||
}); | ||
describe("cryptohat._zeroPad", function() { | ||
it("works when length is count", function() { | ||
expect(zeroPad("", 0)).to.equal(""); | ||
expect(zeroPad("a", 1)).to.equal("a"); | ||
expect(zeroPad("abc", 3)).to.equal("abc"); | ||
}); | ||
it("works when count exceeds length by 1", function() { | ||
expect(zeroPad("", 1)).to.equal("0"); | ||
expect(zeroPad("a", 2)).to.equal("0a"); | ||
expect(zeroPad("abc", 4)).to.equal("0abc"); | ||
}); | ||
it("works when count exceeds length by 2", function() { | ||
expect(zeroPad("", 2)).to.equal("00"); | ||
expect(zeroPad("a", 3)).to.equal("00a"); | ||
expect(zeroPad("abc", 5)).to.equal("00abc"); | ||
}); | ||
it("works when count exceeds length by 7", function() { | ||
expect(zeroPad("", 7)).to.equal("0000000"); | ||
expect(zeroPad("a", 8)).to.equal("0000000a"); | ||
expect(zeroPad("abc", 10)).to.equal("0000000abc"); | ||
}); | ||
it("works when length exceeds count", function() { | ||
expect(zeroPad("a", 0)).to.equal("a"); | ||
expect(zeroPad("abc", 2)).to.equal("abc"); | ||
}); | ||
}); | ||
describe("cryptohat._array32ToString", function() { | ||
@@ -47,2 +78,4 @@ describe("with base == 16", function() { | ||
new Array(24))).to.equal("ffffffffffffffffffffffff"); | ||
expect(array32ToString([0xffffffff, 0x0, 0xffffffff], 16, | ||
new Array(24))).to.equal("ffffffff00000000ffffffff"); | ||
}); | ||
@@ -93,1 +126,33 @@ }); | ||
}); | ||
describe("cryptohat._array32ToHexString", function() { | ||
it("works on zero", function() { | ||
expect(array32ToHexString([0], 16, new Array(4))).to.equal("0000"); | ||
expect(array32ToHexString([0, 0], 16, new Array(4))).to.equal("0000"); | ||
}); | ||
it("works on one-element arrays", function() { | ||
expect(array32ToHexString([0x1], 16, new Array(4))).to.equal("0001"); | ||
expect(array32ToHexString([0xf], 16, new Array(4))).to.equal("000f"); | ||
expect(array32ToHexString([0x1234], 16, new Array(8))).to.equal( | ||
"00001234"); | ||
expect(array32ToHexString([0x12345678], 16, new Array(8))).to.equal( | ||
"12345678"); | ||
expect(array32ToHexString([0xffffffff], 16, new Array(8))).to.equal( | ||
"ffffffff"); | ||
}); | ||
it("works on multi-element arrays", function() { | ||
expect(array32ToHexString([0xa, 0x12345678], 16, new Array(12))).to.equal( | ||
"000a12345678"); | ||
expect(array32ToHexString([0xfedc, 0xba987654], 16, new Array(12))).to. | ||
equal("fedcba987654"); | ||
expect(array32ToHexString([0xffffffff, 0xffffffff, 0xffffffff], 16, | ||
new Array(24))).to.equal("ffffffffffffffffffffffff"); | ||
expect(array32ToHexString([0xffffffff, 0x0, 0xffffffff], 16, | ||
new Array(24))).to.equal("ffffffff00000000ffffffff"); | ||
}); | ||
}); | ||
})(); |
Sorry, the diff of this file is not supported yet
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances in 1 package
41110
14
831
145
0