├── examples └── .empty ├── .npmignore ├── test ├── mocha.opts └── index.js ├── index.js ├── .gitignore ├── .travis.yml ├── package.json ├── bin └── randomstring ├── LICENSE ├── lib ├── charset.js └── randomstring.js ├── CHANGELOG.md └── README.md /examples/.empty: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | test.js -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --reporter spec 2 | --ui tdd 3 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require("./lib/randomstring"); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | node_modules 4 | package-lock.json 5 | test.js -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.12" 4 | - "0.11" 5 | - "0.10" 6 | - "iojs" 7 | - "iojs-v1.0.4" -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "randomstring", 3 | "version": "1.3.1", 4 | "author": "Elias Klughammer (http://www.klughammer.com)", 5 | "description": "A module for generating random strings", 6 | "homepage": "https://github.com/klughammer/node-randomstring", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/klughammer/node-randomstring.git" 10 | }, 11 | "main": "./index", 12 | "engines": { 13 | "node": "*" 14 | }, 15 | "dependencies": { 16 | "randombytes": "2.1.0" 17 | }, 18 | "devDependencies": { 19 | "mocha": "^10.0.0" 20 | }, 21 | "license": "MIT", 22 | "scripts": { 23 | "test": "mocha" 24 | }, 25 | "bin": "bin/randomstring" 26 | } 27 | -------------------------------------------------------------------------------- /bin/randomstring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var randomstring = require('..'); 4 | 5 | var options = {}; 6 | 7 | for (var i = 2, ii = process.argv.length; i < ii; i++) { 8 | var arg = process.argv[i]; 9 | 10 | if (arg.search('length') >= 0) { 11 | var length = arg.split('=')[1]; 12 | 13 | if (length) 14 | options.length = parseInt(length); 15 | } 16 | else if (arg.search('readable') >= 0) { 17 | options.readable = true; 18 | } 19 | else if (arg.search('charset') >= 0) { 20 | var charset = arg.split('=')[1]; 21 | 22 | if (charset) 23 | options.charset = charset; 24 | } 25 | else if (arg.search('capitalization') >= 0) { 26 | var capitalization = arg.split('=')[1]; 27 | 28 | if (capitalization) 29 | options.capitalization = capitalization; 30 | } 31 | // If only a number is given as an arg, parse it as the length 32 | else if (arg.search(/\D/ig) < 0) { 33 | options.length = parseInt(arg); 34 | } 35 | }; 36 | 37 | var result = randomstring.generate(options); 38 | 39 | console.log(result) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Elias Klughammer 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /lib/charset.js: -------------------------------------------------------------------------------- 1 | function Charset() { 2 | this.chars = ''; 3 | } 4 | 5 | Charset.prototype.setType = function(type) { 6 | if (Array.isArray(type)) { 7 | for (var i=0; i < type.length; i++) { 8 | this.chars += this.getCharacters(type[i]); 9 | } 10 | } 11 | else { 12 | this.chars = this.getCharacters(type); 13 | } 14 | } 15 | 16 | Charset.prototype.getCharacters = function(type) { 17 | var chars; 18 | 19 | var numbers = '0123456789'; 20 | var charsLower = 'abcdefghijklmnopqrstuvwxyz'; 21 | var charsUpper = charsLower.toUpperCase(); 22 | var hexChars = 'abcdef'; 23 | var binaryChars = '01'; 24 | var octalChars = '01234567'; 25 | 26 | if (type === 'alphanumeric') { 27 | chars = numbers + charsLower + charsUpper; 28 | } 29 | else if (type === 'numeric') { 30 | chars = numbers; 31 | } 32 | else if (type === 'alphabetic') { 33 | chars = charsLower + charsUpper; 34 | } 35 | else if (type === 'hex') { 36 | chars = numbers + hexChars; 37 | } 38 | else if (type === 'binary') { 39 | chars = binaryChars; 40 | } 41 | else if (type === 'octal') { 42 | chars = octalChars; 43 | } 44 | else { 45 | chars = type; 46 | } 47 | 48 | return chars; 49 | } 50 | 51 | Charset.prototype.removeUnreadable = function() { 52 | var unreadableChars = /[0OIl]/g; 53 | this.chars = this.chars.replace(unreadableChars, ''); 54 | } 55 | 56 | Charset.prototype.setcapitalization = function(capitalization) { 57 | if (capitalization === 'uppercase') { 58 | this.chars = this.chars.toUpperCase(); 59 | } 60 | else if (capitalization === 'lowercase') { 61 | this.chars = this.chars.toLowerCase(); 62 | } 63 | } 64 | 65 | Charset.prototype.removeDuplicates = function() { 66 | var charMap = this.chars.split(''); 67 | charMap = [...new Set(charMap)]; 68 | this.chars = charMap.join(''); 69 | } 70 | 71 | module.exports = exports = Charset; 72 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 1.3.1 / Jan 10, 2025 2 | ================== 3 | * Updated randombytes 4 | 5 | 1.3.0 / Jun 02, 2023 6 | ================== 7 | * Added support for multiple character sets 8 | * Removed dependency on array-uniq 9 | 10 | 1.2.3 / Oct 20, 2022 11 | ================== 12 | * Fixed React support 13 | * Fixed unexpected behavior of length option 14 | 15 | 1.2.2 / Jan 22, 2022 16 | ================== 17 | * Fixed browser support 18 | 19 | 1.2.1 / May 10, 2021 20 | ================== 21 | * Fixed tests 22 | 23 | 1.2.0 / May 10, 2021 24 | ================== 25 | * Use randombytes instead of node.crypto to prevent biased output 26 | * Add support for async generation 27 | * Support for binary and octal charsets 28 | 29 | 1.1.5 / May 18, 2016 30 | ================== 31 | * Optimized character generation algorithm 32 | 33 | 1.1.4 / Feb 10, 2016 34 | ================== 35 | * Added option for capitalization 36 | 37 | 1.1.3 / Nov 03, 2015 38 | ================== 39 | * Fixed test 40 | 41 | 1.1.2 / Nov 03, 2015 42 | ================== 43 | * Added command line support 44 | * Fixed bug causing the "readable" option to fail 45 | 46 | 1.1.0 / Sep 06, 2015 47 | ================== 48 | * Added support for custom character sets 49 | * Added option for excluding poorly readable characters 50 | 51 | 1.0.8 / Sep 01, 2015 52 | ================== 53 | * Avoid problems if crypto.randomBytes throws an exception 54 | 55 | 1.0.7 / Jul 03, 2015 56 | ================== 57 | * Use node.crypto instead of Math.random as random number generator 58 | 59 | 1.0.6 / Jun 01, 2015 60 | ================== 61 | * Added licence for npmjs.org 62 | * Enhanced readme for Github and npm 63 | 64 | 1.0.5 / Apr 03, 2015 65 | ================== 66 | * Better charset setting → Less error-proneness 67 | 68 | 1.0.4 / Apr 03, 2015 69 | ================== 70 | * Added tests 71 | 72 | 1.0.3 / Feb 17, 2014 73 | ================== 74 | * Fixed typo in character set 75 | 76 | 1.0.0 / Jan 21, 2012 77 | ================== 78 | * Start of the project -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-randomstring 2 | 3 | [![Build Status](https://travis-ci.org/klughammer/node-randomstring.svg?branch=master)](https://travis-ci.org/klughammer/node-randomstring) [![Download Stats](https://img.shields.io/npm/dm/randomstring.svg)](https://github.com/klughammer/node-randomstring) 4 | 5 | Library to help you create random strings. 6 | 7 | ## Installation 8 | 9 | To install randomstring, use [npm](http://github.com/npm/npm): 10 | 11 | ``` 12 | npm install randomstring 13 | ``` 14 | 15 | ## Usage 16 | 17 | ```javascript 18 | var randomstring = require("randomstring"); 19 | 20 | randomstring.generate(); 21 | // >> "XwPp9xazJ0ku5CZnlmgAx2Dld8SHkAeT" 22 | 23 | randomstring.generate(7); 24 | // >> "xqm5wXX" 25 | 26 | randomstring.generate({ 27 | length: 12, 28 | charset: 'alphabetic' 29 | }); 30 | // >> "AqoTIzKurxJi" 31 | 32 | randomstring.generate({ 33 | charset: 'abc' 34 | }); 35 | // >> "accbaabbbbcccbccccaacacbbcbbcbbc" 36 | 37 | randomstring.generate({ 38 | charset: ['numeric', '!'] 39 | }); 40 | // >> "145132!87663611567!2486211!07856" 41 | 42 | randomstring.generate({ 43 | charset: 'abc' 44 | }, cb); 45 | // >> "cb(generatedString) {}" 46 | 47 | ``` 48 | 49 | ## API 50 | 51 | `randomstring.` 52 | 53 | - `generate(options, cb)` 54 | - `options` 55 | - `length` - the length of the random string. (default: 32) [OPTIONAL] 56 | - `readable` - exclude poorly readable chars: 0OIl. (default: false) [OPTIONAL] 57 | - `charset` - define the character set for the string. (default: 'alphanumeric') [OPTIONAL] 58 | - `alphanumeric` - [0-9 a-z A-Z] 59 | - `alphabetic` - [a-z A-Z] 60 | - `numeric` - [0-9] 61 | - `hex` - [0-9 a-f] 62 | - `binary` - [01] 63 | - `octal` - [0-7] 64 | - `custom` - any given characters 65 | - `[]` - An array of any above 66 | - `capitalization` - define whether the output should be lowercase / uppercase only. (default: null) [OPTIONAL] 67 | - `lowercase` 68 | - `uppercase` 69 | - `cb` - Optional. If provided uses async version of `crypto.randombytes` 70 | 71 | ## Command Line Usage 72 | 73 | $ npm install -g randomstring 74 | 75 | $ randomstring 76 | > sKCx49VgtHZ59bJOTLcU0Gr06ogUnDJi 77 | 78 | $ randomstring 7 79 | > CpMg433 80 | 81 | $ randomstring length=24 charset=github readable 82 | > hthbtgiguihgbuttuutubugg 83 | 84 | ## Tests 85 | 86 | ``` 87 | npm install 88 | npm test 89 | ``` 90 | 91 | ## LICENSE 92 | 93 | node-randomstring is licensed under the MIT license. 94 | -------------------------------------------------------------------------------- /lib/randomstring.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var randomBytes = require('randombytes'); 4 | var Charset = require('./charset.js'); 5 | 6 | 7 | function unsafeRandomBytes(length) { 8 | var stack = []; 9 | for (var i = 0; i < length; i++) { 10 | stack.push(Math.floor(Math.random() * 255)); 11 | } 12 | 13 | return { 14 | length, 15 | readUInt8: function (index) { 16 | return stack[index]; 17 | } 18 | }; 19 | } 20 | 21 | function safeRandomBytes(length) { 22 | try { 23 | return randomBytes(length); 24 | } catch (e) { 25 | /* React/React Native Fix + Eternal loop removed */ 26 | return unsafeRandomBytes(length); 27 | } 28 | } 29 | 30 | function processString(buf, initialString, chars, reqLen, maxByte) { 31 | var string = initialString; 32 | for (var i = 0; i < buf.length && string.length < reqLen; i++) { 33 | var randomByte = buf.readUInt8(i); 34 | if (randomByte < maxByte) { 35 | string += chars.charAt(randomByte % chars.length); 36 | } 37 | } 38 | return string; 39 | } 40 | 41 | function getAsyncString(string, chars, length, maxByte, cb) { 42 | randomBytes(length, function(err, buf) { 43 | if (err) { 44 | // Since it is waiting for entropy, errors are legit and we shouldn't just keep retrying 45 | cb(err); 46 | } 47 | var generatedString = processString(buf, string, chars, length, maxByte); 48 | if (generatedString.length < length) { 49 | getAsyncString(generatedString, chars, length, maxByte, cb); 50 | } else { 51 | cb(null, generatedString); 52 | } 53 | }) 54 | } 55 | 56 | exports.generate = function(options, cb) { 57 | var charset = new Charset(); 58 | 59 | var length, chars, capitalization, string = ''; 60 | 61 | // Handle options 62 | if (typeof options === 'object') { 63 | length = typeof options.length === 'number' ? options.length : 32; 64 | 65 | if (options.charset) { 66 | charset.setType(options.charset); 67 | } 68 | else { 69 | charset.setType('alphanumeric'); 70 | } 71 | 72 | if (options.capitalization) { 73 | charset.setcapitalization(options.capitalization); 74 | } 75 | 76 | if (options.readable) { 77 | charset.removeUnreadable(); 78 | } 79 | 80 | charset.removeDuplicates(); 81 | } 82 | else if (typeof options === 'number') { 83 | length = options; 84 | charset.setType('alphanumeric'); 85 | } 86 | else { 87 | length = 32; 88 | charset.setType('alphanumeric'); 89 | } 90 | 91 | // Generate the string 92 | var charsLen = charset.chars.length; 93 | var maxByte = 256 - (256 % charsLen); 94 | 95 | if (!cb) { 96 | while (string.length < length) { 97 | var buf = safeRandomBytes(Math.ceil(length * 256 / maxByte)); 98 | string = processString(buf, string, charset.chars, length, maxByte); 99 | } 100 | 101 | return string; 102 | } 103 | 104 | getAsyncString(string, charset.chars, length, maxByte, cb); 105 | 106 | }; 107 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var assert = require("assert"); 4 | var random = require("..").generate; 5 | var testLength = 24700; 6 | 7 | describe("randomstring.generate(options)", function() { 8 | 9 | it("returns a string", function() { 10 | var rds = random(); 11 | assert.equal(typeof(rds), "string"); 12 | }); 13 | 14 | it("returns a string async", function(done) { 15 | random(undefined, function(err, string) { 16 | assert.equal(typeof(string), "string"); 17 | done(); 18 | }); 19 | }); 20 | 21 | it("defaults to 32 characters in length", function() { 22 | assert.equal(random().length, 32); 23 | }); 24 | 25 | it("accepts length as an optional first argument", function() { 26 | assert.equal(random(10).length, 10); 27 | assert.equal(random(0).length, 0); 28 | }); 29 | 30 | it("accepts length as an option param", function() { 31 | assert.equal(random({ length: 7 }).length, 7); 32 | assert.equal(random({ length: 0 }).length, 0); 33 | }); 34 | 35 | it("accepts length as an option param async", function(done) { 36 | random(({ length: 7 }), function(err, string) { 37 | assert.equal(string.length, 7); 38 | done(); 39 | }); 40 | }); 41 | 42 | it("accepts 'numeric' as charset option async", function(done) { 43 | random({ length: testLength, charset: 'numeric' }, function(err, testData) { 44 | assert.equal(testData.length, testLength); 45 | var search = testData.search(/\D/ig); 46 | assert.equal(search, -1); 47 | done(); 48 | }); 49 | }); 50 | 51 | it("accepts 'alphabetic' as charset option async", function(done) { 52 | var testData = random({ length: testLength, charset: 'alphabetic' }, function(err, testData) { 53 | var search = testData.search(/\d/ig); 54 | assert.equal(search, -1); 55 | done(); 56 | }); 57 | }); 58 | 59 | it("accepts 'hex' as charset option", function() { 60 | var testData = random({ length: testLength, charset: 'hex' }); 61 | var search = testData.search(/[^0-9a-f]/ig); 62 | assert.equal(search, -1); 63 | }); 64 | 65 | it("accepts 'binary' as charset option", function() { 66 | var testData = random({ length: testLength, charset: 'binary' }); 67 | var search = testData.search(/[^01]/ig); 68 | assert.equal(search, -1); 69 | }); 70 | 71 | it("accepts 'octal' as charset option", function() { 72 | var testData = random({ length: testLength, charset: 'octal' }); 73 | var search = testData.search(/[^0-7]/ig); 74 | assert.equal(search, -1); 75 | }); 76 | 77 | it("accepts custom charset", function() { 78 | var charset = "abc"; 79 | var testData = random({ length: testLength, charset: charset }); 80 | var search = testData.search(/[^abc]/ig); 81 | assert.equal(search, -1); 82 | }); 83 | 84 | it("accepts custom charset async", function(done) { 85 | var charset = "abc"; 86 | random({ length: testLength, charset: charset }, function(err, testData) { 87 | var search = testData.search(/[^abc]/ig); 88 | assert.equal(search, -1); 89 | done(); 90 | }); 91 | }); 92 | 93 | it("accepts an array of charsets", function() { 94 | var charset = ['alphabetic', '!']; 95 | var testData = random({ length: testLength, charset: charset }); 96 | var search = testData.search(/[^A-Za-z!]/ig); 97 | assert.equal(search, -1); 98 | }); 99 | 100 | it("accepts an array of charsets async", function(done) { 101 | var charset = ['alphabetic', '!']; 102 | random({ length: testLength, charset: charset }, function(err, testData) { 103 | var search = testData.search(/[^A-Za-z!]/ig); 104 | assert.equal(search, -1); 105 | done(); 106 | }); 107 | }); 108 | 109 | it("accepts readable option", function() { 110 | var testData = random({ length: testLength, readable: true }); 111 | var search = testData.search(/[0OIl]/g); 112 | assert.equal(search, -1); 113 | }); 114 | 115 | it("accepts 'uppercase' as capitalization option", function() { 116 | var testData = random({ length: testLength, capitalization: 'uppercase'}); 117 | var search = testData.search(/[a-z]/g); 118 | assert.equal(search, -1); 119 | }); 120 | 121 | it("accepts 'lowercase' as capitalization option", function() { 122 | var testData = random({ length: testLength, capitalization: 'lowercase'}); 123 | var search = testData.search(/[A-Z]/g); 124 | assert.equal(search, -1); 125 | }); 126 | 127 | it("returns unique strings", function() { 128 | var results = {}; 129 | for (var i = 0; i < 1000; i++) { 130 | var s = random(); 131 | assert.notEqual(results[s], true); 132 | results[s] = true; 133 | } 134 | return true; 135 | }); 136 | 137 | it("returns unique strings async", function(done) { 138 | var results = []; 139 | function doTest() { 140 | random(undefined, function(err, string) { 141 | assert.equal(results.indexOf(string), -1); 142 | results.push(string); 143 | if (results.length >= 1000) { 144 | done(); 145 | } else { 146 | doTest(); 147 | } 148 | }); 149 | } 150 | 151 | doTest(); 152 | }); 153 | 154 | it("returns unbiased strings", function() { 155 | var charset = 'abcdefghijklmnopqrstuvwxyz'; 156 | var slen = 100000; 157 | var s = random({ charset: charset, length: slen }); 158 | var counts = {}; 159 | for (var i = 0; i < s.length; i++) { 160 | var c = s.charAt(i); 161 | if (typeof counts[c] === "undefined") { 162 | counts[c] = 0; 163 | } else { 164 | counts[c]++; 165 | } 166 | } 167 | var avg = slen / charset.length; 168 | Object.keys(counts).sort().forEach(function(k) { 169 | var diff = counts[k] / avg; 170 | assert(diff > 0.95 && diff < 1.05, 171 | "bias on `" + k + "': expected average is " + avg + ", got " + counts[k]); 172 | }); 173 | }); 174 | 175 | it("returns unbiased strings async", function(done) { 176 | var charset = 'abcdefghijklmnopqrstuvwxyz'; 177 | var slen = 100000; 178 | random({ charset: charset, length: slen }, function(err, s) { 179 | var counts = {}; 180 | for (var i = 0; i < s.length; i++) { 181 | var c = s.charAt(i); 182 | if (typeof counts[c] === "undefined") { 183 | counts[c] = 0; 184 | } else { 185 | counts[c]++; 186 | } 187 | } 188 | var avg = slen / charset.length; 189 | Object.keys(counts).sort().forEach(function(k) { 190 | var diff = counts[k] / avg; 191 | assert(diff > 0.95 && diff < 1.05, 192 | "bias on `" + k + "': expected average is " + avg + ", got " + counts[k]); 193 | }); 194 | done(); 195 | }); 196 | 197 | }); 198 | }); 199 | --------------------------------------------------------------------------------