randomstring
Advanced tools
Comparing version 1.1.5 to 1.2.0
@@ -0,1 +1,7 @@ | ||
1.2.0 / May 10, 2021 | ||
================== | ||
* Use randombytes instead of node.crypto to prevent biased output | ||
* Add support for async generation | ||
* Support for binary and octal charsets | ||
1.1.5 / May 18, 2016 | ||
@@ -2,0 +8,0 @@ ================== |
@@ -9,8 +9,10 @@ var arrayUniq = require('array-uniq'); | ||
var chars; | ||
var numbers = '0123456789'; | ||
var charsLower = 'abcdefghijklmnopqrstuvwxyz'; | ||
var charsUpper = charsLower.toUpperCase(); | ||
var hexChars = 'abcdef'; | ||
var numbers = '0123456789'; | ||
var charsLower = 'abcdefghijklmnopqrstuvwxyz'; | ||
var charsUpper = charsLower.toUpperCase(); | ||
var hexChars = 'abcdef'; | ||
var binaryChars = '01'; | ||
var octalChars = '01234567'; | ||
if (type === 'alphanumeric') { | ||
@@ -28,6 +30,12 @@ chars = numbers + charsLower + charsUpper; | ||
} | ||
else if (type === 'binary') { | ||
chars = binaryChars; | ||
} | ||
else if (type === 'octal') { | ||
chars = octalChars; | ||
} | ||
else { | ||
chars = type; | ||
} | ||
this.chars = chars; | ||
@@ -56,2 +64,2 @@ } | ||
module.exports = exports = Charset; | ||
module.exports = exports = Charset; |
"use strict"; | ||
var crypto = require('crypto'); | ||
var randomBytes = require('randombytes'); | ||
var Charset = require('./charset.js'); | ||
@@ -9,3 +9,3 @@ | ||
try { | ||
return crypto.randomBytes(length); | ||
return randomBytes(length); | ||
} catch(e) { | ||
@@ -17,12 +17,37 @@ continue; | ||
exports.generate = function(options) { | ||
function processString(buf, initialString, chars, reqLen, maxByte) { | ||
var string = initialString; | ||
for (var i = 0; i < buf.length && string.length < reqLen; i++) { | ||
var randomByte = buf.readUInt8(i); | ||
if (randomByte < maxByte) { | ||
string += chars.charAt(randomByte % chars.length); | ||
} | ||
} | ||
return string; | ||
} | ||
function getAsyncString(string, chars, length, maxByte, cb) { | ||
crypto.randomBytes(length, function(err, buf) { | ||
if (err) { | ||
// Since it is waiting for entropy, errors are legit and we shouldn't just keep retrying | ||
cb(err); | ||
} | ||
var generatedString = processString(buf, string, chars, length, maxByte); | ||
if (generatedString.length < length) { | ||
getAsyncString(generatedString, chars, length, maxByte, cb); | ||
} else { | ||
cb(null, generatedString); | ||
} | ||
}) | ||
} | ||
exports.generate = function(options, cb) { | ||
var charset = new Charset(); | ||
var length, chars, capitalization, string = ''; | ||
// Handle options | ||
if (typeof options === 'object') { | ||
length = options.length || 32; | ||
if (options.charset) { | ||
@@ -34,11 +59,11 @@ charset.setType(options.charset); | ||
} | ||
if (options.capitalization) { | ||
charset.setcapitalization(options.capitalization); | ||
} | ||
if (options.readable) { | ||
charset.removeUnreadable(); | ||
} | ||
charset.removeDuplicates(); | ||
@@ -54,18 +79,18 @@ } | ||
} | ||
// Generate the string | ||
var charsLen = charset.chars.length; | ||
var maxByte = 256 - (256 % charsLen); | ||
while (length > 0) { | ||
var buf = safeRandomBytes(Math.ceil(length * 256 / maxByte)); | ||
for (var i = 0; i < buf.length && length > 0; i++) { | ||
var randomByte = buf.readUInt8(i); | ||
if (randomByte < maxByte) { | ||
string += charset.chars.charAt(randomByte % charsLen); | ||
length--; | ||
} | ||
if (!cb) { | ||
while (string.length < length) { | ||
var buf = safeRandomBytes(Math.ceil(length * 256 / maxByte)); | ||
string = processString(buf, string, charset.chars, length, maxByte); | ||
} | ||
return string; | ||
} | ||
return string; | ||
getAsyncString(string, charset.chars, length, maxByte, cb); | ||
}; |
{ | ||
"name": "randomstring", | ||
"version": "1.1.5", | ||
"version": "1.2.0", | ||
"author": "Elias Klughammer <elias@klughammer.com> (http://www.klughammer.com)", | ||
@@ -16,3 +16,4 @@ "description": "A module for generating random strings", | ||
"dependencies": { | ||
"array-uniq": "1.0.2" | ||
"array-uniq": "1.0.2", | ||
"randombytes": "2.0.3" | ||
}, | ||
@@ -19,0 +20,0 @@ "devDependencies": { |
@@ -36,2 +36,8 @@ # node-randomstring | ||
// >> "accbaabbbbcccbccccaacacbbcbbcbbc" | ||
randomstring.generate({ | ||
charset: 'abc' | ||
}, cb); | ||
// >> "cb(generatedString) {}" | ||
``` | ||
@@ -43,14 +49,18 @@ | ||
- `generate(options)` | ||
- `length` - the length of the random string. (default: 32) [OPTIONAL] | ||
- `readable` - exclude poorly readable chars: 0OIl. (default: false) [OPTIONAL] | ||
- `charset` - define the character set for the string. (default: 'alphanumeric') [OPTIONAL] | ||
- `alphanumeric` - [0-9 a-z A-Z] | ||
- `alphabetic` - [a-z A-Z] | ||
- `numeric` - [0-9] | ||
- `hex` - [0-9 a-f] | ||
- `custom` - any given characters | ||
- `capitalization` - define whether the output should be lowercase / uppercase only. (default: null) [OPTIONAL] | ||
- `lowercase` | ||
- `uppercase` | ||
- `generate(options, cb)` | ||
- `options` | ||
- `length` - the length of the random string. (default: 32) [OPTIONAL] | ||
- `readable` - exclude poorly readable chars: 0OIl. (default: false) [OPTIONAL] | ||
- `charset` - define the character set for the string. (default: 'alphanumeric') [OPTIONAL] | ||
- `alphanumeric` - [0-9 a-z A-Z] | ||
- `alphabetic` - [a-z A-Z] | ||
- `numeric` - [0-9] | ||
- `hex` - [0-9 a-f] | ||
- `binary` - [01] | ||
- `octal` - [0-7] | ||
- `custom` - any given characters | ||
- `capitalization` - define whether the output should be lowercase / uppercase only. (default: null) [OPTIONAL] | ||
- `lowercase` | ||
- `uppercase` | ||
- `cb` - Optional. If provided uses async version of `crypto.randombytes` | ||
@@ -66,3 +76,3 @@ ## Command Line Usage | ||
> CpMg433 | ||
$ randomstring length=24 charset=github readable | ||
@@ -69,0 +79,0 @@ > hthbtgiguihgbuttuutubugg |
@@ -14,2 +14,9 @@ "use strict"; | ||
it("returns a string async", function(done) { | ||
random(undefined, function(err, string) { | ||
assert.equal(typeof(string), "string"); | ||
done(); | ||
}); | ||
}); | ||
it("defaults to 32 characters in length", function() { | ||
@@ -22,19 +29,31 @@ assert.equal(random().length, 32); | ||
}); | ||
it("accepts length as an option param", function() { | ||
assert.equal(random({ length: 7 }).length, 7); | ||
}); | ||
it("accepts 'numeric' as charset option", function() { | ||
var testData = random({ length: testLength, charset: 'numeric' }); | ||
var search = testData.search(/\D/ig); | ||
assert.equal(search, -1); | ||
it("accepts length as an option param async", function(done) { | ||
random(({ length: 7 }), function(err, string) { | ||
assert.equal(string.length, 7); | ||
done(); | ||
}); | ||
}); | ||
it("accepts 'numeric' as charset option async", function(done) { | ||
random({ length: testLength, charset: 'numeric' }, function(err, testData) { | ||
assert.equal(testData.length, testLength); | ||
var search = testData.search(/\D/ig); | ||
assert.equal(search, -1); | ||
done(); | ||
}); | ||
}); | ||
it("accepts 'alphabetic' as charset option", function() { | ||
var testData = random({ length: testLength, charset: 'alphabetic' }); | ||
var search = testData.search(/\d/ig); | ||
assert.equal(search, -1); | ||
it("accepts 'alphabetic' as charset option async", function(done) { | ||
var testData = random({ length: testLength, charset: 'alphabetic' }, function(err, testData) { | ||
var search = testData.search(/\d/ig); | ||
assert.equal(search, -1); | ||
done(); | ||
}); | ||
}); | ||
it("accepts 'hex' as charset option", function() { | ||
@@ -46,2 +65,14 @@ var testData = random({ length: testLength, charset: 'hex' }); | ||
it("accepts 'binary' as charset option", function() { | ||
var testData = random({ length: testLength, charset: 'binary' }); | ||
var search = testData.search(/[^01]/ig); | ||
assert.equal(search, -1); | ||
}); | ||
it("accepts 'octal' as charset option", function() { | ||
var testData = random({ length: testLength, charset: 'octal' }); | ||
var search = testData.search(/[^0-7]/ig); | ||
assert.equal(search, -1); | ||
}); | ||
it("accepts custom charset", function() { | ||
@@ -53,2 +84,11 @@ var charset = "abc"; | ||
}); | ||
it("accepts custom charset async", function(done) { | ||
var charset = "abc"; | ||
random({ length: testLength, charset: charset }, function(err, testData) { | ||
var search = testData.search(/[^abc]/ig); | ||
assert.equal(search, -1); | ||
done(); | ||
}); | ||
}); | ||
@@ -60,3 +100,3 @@ it("accepts readable option", function() { | ||
}); | ||
it("accepts 'uppercase' as capitalization option", function() { | ||
@@ -67,3 +107,3 @@ var testData = random({ length: testLength, capitalization: 'uppercase'}); | ||
}); | ||
it("accepts 'lowercase' as capitalization option", function() { | ||
@@ -85,2 +125,19 @@ var testData = random({ length: testLength, capitalization: 'lowercase'}); | ||
it("returns unique strings async", function(done) { | ||
var results = []; | ||
function doTest() { | ||
random(undefined, function(err, string) { | ||
assert.equal(results.indexOf(string), -1); | ||
results.push(string); | ||
if (results.length >= 1000) { | ||
done(); | ||
} else { | ||
doTest(); | ||
} | ||
}); | ||
} | ||
doTest(); | ||
}); | ||
it("returns unbiased strings", function() { | ||
@@ -107,2 +164,25 @@ var charset = 'abcdefghijklmnopqrstuvwxyz'; | ||
it("returns unbiased strings async", function(done) { | ||
var charset = 'abcdefghijklmnopqrstuvwxyz'; | ||
var slen = 100000; | ||
random({ charset: charset, length: slen }, function(err, s) { | ||
var counts = {}; | ||
for (var i = 0; i < s.length; i++) { | ||
var c = s.charAt(i); | ||
if (typeof counts[c] === "undefined") { | ||
counts[c] = 0; | ||
} else { | ||
counts[c]++; | ||
} | ||
} | ||
var avg = slen / charset.length; | ||
Object.keys(counts).sort().forEach(function(k) { | ||
var diff = counts[k] / avg; | ||
assert(diff > 0.95 && diff < 1.05, | ||
"bias on `" + k + "': expected average is " + avg + ", got " + counts[k]); | ||
}); | ||
done(); | ||
}); | ||
}); | ||
}); |
14870
285
88
2
12
+ Addedrandombytes@2.0.3
+ Addedrandombytes@2.0.3(transitive)