Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at InVision In Real Life (IRL) 2018 (Hollywood, CA) with: David Fraga and Clark Valberg
Ben Nadel at InVision In Real Life (IRL) 2018 (Hollywood, CA) with: David Fraga@davidfraga ) and Clark Valberg@clarkvalberg )

Randomly Selecting RegEx Day Winners Using A Cryptographically Secure Pseudo-Random Number Generator (CSPRNG) In Node.js

By Ben Nadel on

Friday was the 11th annual Regular Expression Day celebration. And, this year, participants submitted Haiku poems about their Regular Expressions experience in an effort to win some gift cards. And, since I take every opportunity I can to learn me some programming, I figured that randomly selecting the winners would be a perfect time to look at cryptographically secure number generation in Node.js.

Out of the box, Node.js provides the Crypto module, which contains a method for generating random bytes: crypto.randomBytes(). The .randomBytes() method generates cryptographically strong pseudo-random data by collecting entropy from the underlying system. It can either block-and-wait for the necessary entropy to become available. Or, it can take a callback that will be invoked asynchronously when the random data eventually becomes available.

At first, I was going to use the crypto module to generate 4-byte Buffers and then use the resultant buffer.readUInt32LE() method to convert those bytes into an integer. I was then going to take that integer and transform it into an array index by using the modulo operator (%). As it turns out, however, this approach can inadvertently bias the outcome towards lower-indices in the array.

To be clear, I am not a security expert. Nor do I play one on TV. But, I found an excellent explanation by Sven Slootweg as to why the modulo operator is a naive way to consume the crypto.randomBytes() output. In his explanation, Sven recommends using his NPM module "random-number-csprng" to generate random numbers within a range without biasing towards lower numbers in the range.

The "random-number-csprng" module is built on top of the core Crypto module. And, it generates random data using the crypto.randomBytes() method, just like I was planning to do. But, it transforms the bytes in such a way that it doesn't bias the results. I think this is a great reminder that you should never re-invent secure algorithms. You will mess it up! Someone else has already built it better, stronger, and more secure than you will with your zero years of security expertise.

That said, I took the "random-number-csprng" module and used it to create a RandomArrayStream class in Node.js that will randomly emit non-repeating values from within a source array:

  • // Import the core node modules.
  • var chalk = require( "chalk" );
  • // I'm using "random-number-csprng" instead of calling crypto.getRandomBytes() directly
  • // so that I don't accidentally bias the results.
  • // --
  • // READ MORE: https://gist.github.com/joepie91/7105003c3b26e65efcea63f3db82dfba
  • var randomNumber = require( "random-number-csprng" );
  •  
  • // ----------------------------------------------------------------------------------- //
  • // ----------------------------------------------------------------------------------- //
  •  
  • class RandomArrayStream {
  •  
  • // I initialize the random array stream using the given collection.
  • constructor( sourceArray ) {
  •  
  • this._sourceArray = [ ...sourceArray ];
  •  
  • }
  •  
  • // ---
  • // PUBLIC METHODS.
  • // ---
  •  
  • // I return the next item in the stream.
  • // --
  • // NOTE: The next() method returns a Promise because it is using a secure pseudo-
  • // random number generator (SPRNG) under the hood, which is generating random bytes
  • // asynchronously as entropy is collected from the operating system.
  • async next() {
  •  
  • if ( ! this._sourceArray.length ) {
  •  
  • throw( new Error( "The array stream is empty." ) );
  •  
  • }
  •  
  • // The randomNumber() method expects an INCLUSIVE range. As such, we have to
  • // provide the maximum index that would be valid to select in the source array.
  • var maxInclusiveIndex = ( this._sourceArray.length - 1 );
  • var randomIndex = await randomNumber( 0, maxInclusiveIndex );
  •  
  • // Now that we have our random index selected, we want to delete it from the
  • // underlying array so that we don't accidentally select in on a subsequent call.
  • // Splice will return the randomly-selected, spliced-out value.
  • var randomValue = this._sourceArray.splice( randomIndex, 1 ).pop();
  •  
  • return( randomValue );
  •  
  • }
  •  
  • }
  •  
  • // ----------------------------------------------------------------------------------- //
  • // ----------------------------------------------------------------------------------- //
  •  
  • // Create a random array stream from our RegEx Day 2018 Haiku participants.
  • var arrayStream = new RandomArrayStream([
  • "Wissam Abirached",
  • "Don Abrams",
  • "Amine Matmati",
  • "Peter Reijnders",
  • "Scott Reynen",
  • "Chandler",
  • "Joshua Miller",
  • "Paul",
  • "Dave Lewis",
  • "Matthew Clemente",
  • "Mark Gregory",
  • "Seven",
  • "Erika Rich",
  • "Dave",
  • "Charles Robertson",
  • "fghberius",
  • "Kevin",
  • "Alice L Mora",
  • "David S"
  • ]);
  •  
  • // NOTE: Since the secure random-number generator runs asynchronously, our calling
  • // context needs to deal with Promises. As such, I'm just creating an self-executing
  • // function expression using the Async / Await syntax.
  • (async function selectWinners() {
  •  
  • // Setup our formatting styles (totally not necessary, but fun to see how the
  • // Chalk formats can be aliased and passed-around).
  • var firstWinner = chalk.green.bold;
  • var runnerUp = chalk.green;
  •  
  • // Randomly select the winners! Woot! :partyparrot:
  • console.log( firstWinner( "1st Winner:", await arrayStream.next() ) );
  • console.log( runnerUp( "2nd Winner:", await arrayStream.next() ) );
  • console.log( runnerUp( "3rd Winner:", await arrayStream.next() ) );
  • console.log( runnerUp( "4th Winner:", await arrayStream.next() ) );
  • console.log( runnerUp( "5th Winner:", await arrayStream.next() ) );
  •  
  • })().catch(
  • function handleError( error ) {
  •  
  • console.log( "There was a problem generating random numbers:" );
  • console.error( error );
  •  
  • }
  • );

NOTE: This RandomArrayStream class is not meant to be generally usable. It is an overly-simplistic abstraction. It was just something to manage the logic of the random number generation and consumption.

Since the "random-number-csprng" module doesn't expose a blocking version of the randomNumber() method, I am using the Async / Await syntax to more easily manage the resulting Promise-based workflow. First, I create my RandomArrayStream instance using the collection of participants. Then, I'm using the "await" operator to select the next winner. And, when we run this code through Node.js, we get the following terminal output:


 
 
 

 
 Regular Expression Day 2018 winners selected through a cryptographically secure pseudo-random number generator (CSPRNG) in Node.js. 
 
 
 

Woot woot! Congratulations to the securely selected random winners!



Looking For A New Job?

Ooops, there are no jobs. Post one now for only $29 and own this real estate!

100% of job board revenue is donated to Kiva. Loans that change livesFind out more »

Reader Comments

@Charles,

As far as I saw, the npm module that I linked to is actually usable on the client-side. That said, you don't necessarily need something "cryptographically secure" in any situation that needs a random number. I used it here mostly for fun and as a learning experience -- and because I thought it would provide better randomization. But, on the client-side, for things like randomly selecting some "default value", I'll just use the Math.random()-based logic.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.