11'use strict' ;
2+ const { promisify} = require ( 'util' ) ;
23const crypto = require ( 'crypto' ) ;
34
5+ const randomBytesAsync = promisify ( crypto . randomBytes ) ;
6+
47const urlSafeCharacters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~' . split ( '' ) ;
58const numericCharacters = '0123456789' . split ( '' ) ;
69const distinguishableCharacters = 'CDEHKMPRTUWXY012458' . split ( '' ) ;
@@ -32,6 +35,40 @@ const generateForCustomCharacters = (length, characters) => {
3235 return string ;
3336} ;
3437
38+ const generateForCustomCharactersAsync = async ( length , characters ) => {
39+ // Generating entropy is faster than complex math operations, so we use the simplest way
40+ const characterCount = characters . length ;
41+ const maxValidSelector = ( Math . floor ( 0x10000 / characterCount ) * characterCount ) - 1 ; // Using values above this will ruin distribution when using modular division
42+ const entropyLength = 2 * Math . ceil ( 1.1 * length ) ; // Generating a bit more than required so chances we need more than one pass will be really low
43+ let string = '' ;
44+ let stringLength = 0 ;
45+
46+ while ( stringLength < length ) { // In case we had many bad values, which may happen for character sets of size above 0x8000 but close to it
47+ const entropy = await randomBytesAsync ( entropyLength ) ; // eslint-disable-line no-await-in-loop
48+ let entropyPosition = 0 ;
49+
50+ while ( entropyPosition < entropyLength && stringLength < length ) {
51+ const entropyValue = entropy . readUInt16LE ( entropyPosition ) ;
52+ entropyPosition += 2 ;
53+ if ( entropyValue > maxValidSelector ) { // Skip values which will ruin distribution when using modular division
54+ continue ;
55+ }
56+
57+ string += characters [ entropyValue % characterCount ] ;
58+ stringLength ++ ;
59+ }
60+ }
61+
62+ return string ;
63+ } ;
64+
65+ const generateRandomBytes = ( byteLength , type , length ) => crypto . randomBytes ( byteLength ) . toString ( type ) . slice ( 0 , length ) ;
66+
67+ const generateRandomBytesAsync = async ( byteLength , type , length ) => {
68+ const buffer = await randomBytesAsync ( byteLength ) ;
69+ return buffer . toString ( type ) . slice ( 0 , length ) ;
70+ } ;
71+
3572const allowedTypes = [
3673 undefined ,
3774 'hex' ,
@@ -41,7 +78,7 @@ const allowedTypes = [
4178 'distinguishable'
4279] ;
4380
44- module . exports = ( { length, type, characters} ) => {
81+ const createGenerator = ( generateForCustomCharacters , generateRandomBytes ) => ( { length, type, characters} ) => {
4582 if ( ! ( length >= 0 && Number . isFinite ( length ) ) ) {
4683 throw new TypeError ( 'Expected a `length` to be a non-negative finite number' ) ;
4784 }
@@ -63,11 +100,11 @@ module.exports = ({length, type, characters}) => {
63100 }
64101
65102 if ( type === 'hex' || ( type === undefined && characters === undefined ) ) {
66- return crypto . randomBytes ( Math . ceil ( length * 0.5 ) ) . toString ( 'hex' ) . slice ( 0 , length ) ; // Need 0.5 byte entropy per character
103+ return generateRandomBytes ( Math . ceil ( length * 0.5 ) , 'hex' , length ) ; // Need 0.5 byte entropy per character
67104 }
68105
69106 if ( type === 'base64' ) {
70- return crypto . randomBytes ( Math . ceil ( length * 0.75 ) ) . toString ( 'base64' ) . slice ( 0 , length ) ; // Need 0.75 byte of entropy per character
107+ return generateRandomBytes ( Math . ceil ( length * 0.75 ) , 'base64' , length ) ; // Need 0.75 byte of entropy per character
71108 }
72109
73110 if ( type === 'url-safe' ) {
@@ -92,3 +129,6 @@ module.exports = ({length, type, characters}) => {
92129
93130 return generateForCustomCharacters ( length , characters . split ( '' ) ) ;
94131} ;
132+
133+ module . exports = createGenerator ( generateForCustomCharacters , generateRandomBytes ) ;
134+ module . exports . async = createGenerator ( generateForCustomCharactersAsync , generateRandomBytesAsync ) ;
0 commit comments