├── test.js ├── .gitignore ├── package.json ├── README.md └── index.js /test.js: -------------------------------------------------------------------------------- 1 | var Random = require('./index.js'); 2 | console.log('test test test'); 3 | //exports.printMsg = function() { 4 | console.log("This is a message from the demo package"); 5 | console.log(Random.secret()); 6 | //}; 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 27 | node_modules 28 | 29 | # Debug log from npm 30 | npm-debug.log 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "meteor-random", 3 | "version": "0.0.3", 4 | "description": "Meteor's Random Package for Straight Node", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node test.js" 8 | }, 9 | "author": "Trever Faden", 10 | "license": "BSD-2-Clause", 11 | "dependencies": { 12 | "crypto": "0.0.3" 13 | }, 14 | "devDependencies": {}, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/RebelMail/meteor-random.git" 18 | }, 19 | "keywords": [ 20 | "random", 21 | "meteor", 22 | "crypto", 23 | "api", 24 | "key", 25 | "generator", 26 | "api", 27 | "keys", 28 | "tokens", 29 | "UUID" 30 | ], 31 | "bugs": { 32 | "url": "https://github.com/RebelMail/meteor-random/issues" 33 | }, 34 | "homepage": "https://github.com/RebelMail/meteor-random" 35 | } 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # meteor-random 2 | Meteor's Random Package, Stripped for Straight Node 3 | 4 | ``` 5 | Random = require('meteor-random'); 6 | ``` 7 | The random package provides several functions for generating random numbers. It uses a cryptographically strong pseudorandom number generator when possible, but falls back to a weaker random number generator when cryptographically strong randomness is not available (on older browsers or on servers that don't have enough entropy to seed the cryptographically strong generator). 8 | 9 | * `Random.id([n])` 10 | Returns a unique identifier, such as "Jjwjg6gouWLXhMGKW", that is likely to be unique in the whole world. The optional argument n specifies the length of the identifier in characters and defaults to 17. 11 | 12 | * `Random.secret([n])` 13 | Returns a random string of printable characters with 6 bits of entropy per character. The optional argument n specifies the length of the secret string and defaults to 43 characters, or 256 bits of entropy. Use Random.secret for security-critical secrets that are intended for machine, rather than human, consumption. 14 | 15 | * `Random.fraction()` 16 | Returns a number between 0 and 1, like Math.random. 17 | 18 | * `Random.choice(arrayOrString)` 19 | Returns a random element of the given array or string. 20 | 21 | * `Random.hexString(n)` 22 | Returns a random string of n hexadecimal digits. 23 | 24 | 25 | For more information see [Meteor's Random Docs](http://docs.meteor.com/#/full/random) 26 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // We use cryptographically strong PRNGs (crypto.getRandomBytes() on the server, 2 | // window.crypto.getRandomValues() in the browser) when available. If these 3 | // PRNGs fail, we fall back to the Alea PRNG, which is not cryptographically 4 | // strong, and we seed it with various sources such as the date, Math.random, 5 | // and window size on the client. When using crypto.getRandomValues(), our 6 | // primitive is hexString(), from which we construct fraction(). When using 7 | // window.crypto.getRandomValues() or alea, the primitive is fraction and we use 8 | // that to construct hex string. 9 | 10 | var nodeCrypto = require('crypto'); 11 | 12 | // see http://baagoe.org/en/wiki/Better_random_numbers_for_javascript 13 | // for a full discussion and Alea implementation. 14 | var Alea = function () { 15 | function Mash() { 16 | var n = 0xefc8249d; 17 | 18 | var mash = function(data) { 19 | data = data.toString(); 20 | for (var i = 0; i < data.length; i++) { 21 | n += data.charCodeAt(i); 22 | var h = 0.02519603282416938 * n; 23 | n = h >>> 0; 24 | h -= n; 25 | h *= n; 26 | n = h >>> 0; 27 | h -= n; 28 | n += h * 0x100000000; // 2^32 29 | } 30 | return (n >>> 0) * 2.3283064365386963e-10; // 2^-32 31 | }; 32 | 33 | mash.version = 'Mash 0.9'; 34 | return mash; 35 | } 36 | 37 | return (function (args) { 38 | var s0 = 0; 39 | var s1 = 0; 40 | var s2 = 0; 41 | var c = 1; 42 | 43 | if (args.length == 0) { 44 | args = [+new Date]; 45 | } 46 | var mash = Mash(); 47 | s0 = mash(' '); 48 | s1 = mash(' '); 49 | s2 = mash(' '); 50 | 51 | for (var i = 0; i < args.length; i++) { 52 | s0 -= mash(args[i]); 53 | if (s0 < 0) { 54 | s0 += 1; 55 | } 56 | s1 -= mash(args[i]); 57 | if (s1 < 0) { 58 | s1 += 1; 59 | } 60 | s2 -= mash(args[i]); 61 | if (s2 < 0) { 62 | s2 += 1; 63 | } 64 | } 65 | mash = null; 66 | 67 | var random = function() { 68 | var t = 2091639 * s0 + c * 2.3283064365386963e-10; // 2^-32 69 | s0 = s1; 70 | s1 = s2; 71 | return s2 = t - (c = t | 0); 72 | }; 73 | random.uint32 = function() { 74 | return random() * 0x100000000; // 2^32 75 | }; 76 | random.fract53 = function() { 77 | return random() + 78 | (random() * 0x200000 | 0) * 1.1102230246251565e-16; // 2^-53 79 | }; 80 | random.version = 'Alea 0.9'; 81 | random.args = args; 82 | return random; 83 | 84 | } (Array.prototype.slice.call(arguments))); 85 | }; 86 | 87 | var UNMISTAKABLE_CHARS = "23456789ABCDEFGHJKLMNPQRSTWXYZabcdefghijkmnopqrstuvwxyz"; 88 | var BASE64_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + 89 | "0123456789-_"; 90 | 91 | // If seeds are provided, then the alea PRNG will be used, since cryptographic 92 | // PRNGs (Node crypto and window.crypto.getRandomValues) don't allow us to 93 | // specify seeds. The caller is responsible for making sure to provide a seed 94 | // for alea if a csprng is not available. 95 | var RandomGenerator = function (seedArray) { 96 | var self = this; 97 | if (seedArray !== undefined) 98 | self.alea = Alea.apply(null, seedArray); 99 | }; 100 | 101 | RandomGenerator.prototype.fraction = function () { 102 | var self = this; 103 | if (self.alea) { 104 | return self.alea(); 105 | } else if (nodeCrypto) { 106 | var numerator = parseInt(self.hexString(8), 16); 107 | return numerator * 2.3283064365386963e-10; // 2^-32 108 | } else if (typeof window !== "undefined" && window.crypto && 109 | window.crypto.getRandomValues) { 110 | var array = new Uint32Array(1); 111 | window.crypto.getRandomValues(array); 112 | return array[0] * 2.3283064365386963e-10; // 2^-32 113 | } else { 114 | throw new Error('No random generator available'); 115 | } 116 | }; 117 | 118 | RandomGenerator.prototype.hexString = function (digits) { 119 | var self = this; 120 | if (nodeCrypto && ! self.alea) { 121 | var numBytes = Math.ceil(digits / 2); 122 | var bytes; 123 | // Try to get cryptographically strong randomness. Fall back to 124 | // non-cryptographically strong if not available. 125 | try { 126 | bytes = nodeCrypto.randomBytes(numBytes); 127 | } catch (e) { 128 | // XXX should re-throw any error except insufficient entropy 129 | bytes = nodeCrypto.pseudoRandomBytes(numBytes); 130 | } 131 | var result = bytes.toString("hex"); 132 | // If the number of digits is odd, we'll have generated an extra 4 bits 133 | // of randomness, so we need to trim the last digit. 134 | return result.substring(0, digits); 135 | } else { 136 | var hexDigits = []; 137 | for (var i = 0; i < digits; ++i) { 138 | hexDigits.push(self.choice("0123456789abcdef")); 139 | } 140 | return hexDigits.join(''); 141 | } 142 | }; 143 | 144 | RandomGenerator.prototype._randomString = function (charsCount, 145 | alphabet) { 146 | var self = this; 147 | var digits = []; 148 | for (var i = 0; i < charsCount; i++) { 149 | digits[i] = self.choice(alphabet); 150 | } 151 | return digits.join(""); 152 | }; 153 | 154 | RandomGenerator.prototype.id = function (charsCount) { 155 | var self = this; 156 | // 17 characters is around 96 bits of entropy, which is the amount of 157 | // state in the Alea PRNG. 158 | if (charsCount === undefined) 159 | charsCount = 17; 160 | 161 | return self._randomString(charsCount, UNMISTAKABLE_CHARS); 162 | }; 163 | 164 | RandomGenerator.prototype.secret = function (charsCount) { 165 | var self = this; 166 | // Default to 256 bits of entropy, or 43 characters at 6 bits per 167 | // character. 168 | if (charsCount === undefined) 169 | charsCount = 43; 170 | return self._randomString(charsCount, BASE64_CHARS); 171 | }; 172 | 173 | RandomGenerator.prototype.choice = function (arrayOrString) { 174 | var index = Math.floor(this.fraction() * arrayOrString.length); 175 | if (typeof arrayOrString === "string") 176 | return arrayOrString.substr(index, 1); 177 | else 178 | return arrayOrString[index]; 179 | }; 180 | 181 | // instantiate RNG. Heuristically collect entropy from various sources when a 182 | // cryptographic PRNG isn't available. 183 | 184 | // client sources 185 | var height = (typeof window !== 'undefined' && window.innerHeight) || 186 | (typeof document !== 'undefined' 187 | && document.documentElement 188 | && document.documentElement.clientHeight) || 189 | (typeof document !== 'undefined' 190 | && document.body 191 | && document.body.clientHeight) || 192 | 1; 193 | 194 | var width = (typeof window !== 'undefined' && window.innerWidth) || 195 | (typeof document !== 'undefined' 196 | && document.documentElement 197 | && document.documentElement.clientWidth) || 198 | (typeof document !== 'undefined' 199 | && document.body 200 | && document.body.clientWidth) || 201 | 1; 202 | 203 | var agent = (typeof navigator !== 'undefined' && navigator.userAgent) || ""; 204 | 205 | if (nodeCrypto || 206 | (typeof window !== "undefined" && 207 | window.crypto && window.crypto.getRandomValues)) 208 | module.exports = Random = new RandomGenerator(); 209 | else 210 | module.exports = Random = new RandomGenerator([new Date(), height, width, agent, Math.random()]); 211 | 212 | Random.createWithSeeds = function () { 213 | if (arguments.length === 0) { 214 | throw new Error('No seeds were provided'); 215 | } 216 | return new RandomGenerator(arguments); 217 | }; 218 | --------------------------------------------------------------------------------