Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at CFinNC 2009 (Raleigh, North Carolina) with: Larry Lyons
Ben Nadel at CFinNC 2009 (Raleigh, North Carolina) with: Larry Lyons

Trying To Generate "Cryptographically Strong" Random Tokens In ColdFusion

By Ben Nadel on
Tags: ColdFusion

WARNING: I am not a security expert. I am simply trying to make heads-and-tails of some of the stuff that I am reading.

Lately, I've been working on some security stuff that requires generating "cryptographically strong" (or cryptographically secure) random values. These are random values that are suitable for cryptography and security purposes. Essentially, that means that these values (and the generators that create them) are sufficiently unpredictable and hold up well to attacks. In Java (and therefore in ColdFusion as well), the java.security.SecureRandom class (and its various implementations) are designed to generate these kind of values.

Before this, I had never dealt with java.security.SecureRandom. So, I went to the Google and found a few solid articles to get me going:

Unfortunately, it was hard to find consistent information on the SecureRandom class. Many articles that I read seemed to contradict each other. As such, I put more faith into the articles that I found on the Cigital Blog since Cigital is one of the world's largest security firms. On their blog, they recommend always explicitly defining the algorithm and provider when getting a SecureRandom instance so that you are sure to be using a consistent implementation across all environments.

In my experimental code, I've opted to use the SHA1PRNG algorithm, instead of the NativePRNG algorithm, because it seems to be a more widely available implementation and is apparently the faster of the two (from what I've read).

In the following code, I've encapsulated the interaction with the SecureRandom class inside of a ColdFusion component that exposes one method - nextToken(). This method returns bas64url-encoded values that are based on a given number of random bytes. I'm using base64url so that the generated tokens can be safely used in a variety of contexts, including, but not limited to, URLs where they can be passed back and forth for request authentication:

  • component
  • output = false
  • hint = "I generate random tokens using Java's SecureRandom class."
  • {
  •  
  • /**
  • * I initialize the token generator.
  • *
  • * @output false
  • */
  • public any function init() {
  •  
  • // I am the generator implementation used to generate the random token data.
  • // --
  • // NOTE: The SHA1PRNG is not the default in all implementations of the JVM. As
  • // such, we're defining the algorithm explicitly to keep this code consistent.
  • generator = createObject( "java", "java.security.SecureRandom" )
  • .getInstance(
  • javaCast( "string", "SHA1PRNG" ),
  • javaCast( "string", "SUN" )
  • )
  • ;
  •  
  • // Now that we've initialized the random generator, we have to generate a random
  • // byte of data. This will ensure that the random generator is self-seeded using
  • // a shared seed generator.
  • // --
  • // CAUTION: Since the underlying seed generator reads from sources of entropy,
  • // this may hang until enough entropy has been collected. This is another good
  • // reason to do it during initialization time rather than at first-use time.
  • generator.nextBytes( charsetDecode( " ", "utf-8" ) );
  •  
  • // I hold the future date at which time the generator should be reseeded in order
  • // to keep it unpredictable.
  • // --
  • // NOTE: This doesn't affect the randomness of the values. But, the thinking
  • // is that the longer the generator is producing values using the same seed,
  • // the more likely an attacker is to be able to determine the original seed by
  • // passively observing generated values.
  • reseedAt = getNextReseedAt();
  •  
  • return( this );
  •  
  • }
  •  
  •  
  • // ---
  • // PUBLIC METHODS.
  • // ---
  •  
  •  
  • /**
  • * I generate "cryptographically strong" random token strings that are based on the
  • * given number of random bytes. The random bytes are subsequently encoded using a
  • * base64url character-set so that they are URL-safe and can be used in a variety of
  • * contexts. And, since base65url is a case-sensitive schema, the tokens will
  • * naturally be case-sensitive.
  • *
  • * @byteCount I am the number of random bytes used to generate the token.
  • * @output false
  • */
  • public string function nextToken( numeric byteCount = 32 ) {
  •  
  • // Check to see if the generator needs to be reseeded (using a double-check
  • // locking approach to reduce the bottleneck).
  • if ( now() >= reseedAt ) {
  •  
  • // Synchronize the reseeding.
  • // --
  • // NOTE: From what I have read, I DON'T BELIEVE that .generateSeed() will
  • // ever hang with the SHA1PRNG algorithm. However, it is unclear to me. As
  • // such, I'm using [throwOnTimeout = false] so that parallel threads won't
  • // error if the lock cannot be obtained in a timely manner and will just
  • // fall through to using the generator with the pre-seeding state.
  • lock
  • name = "TokenGenerator.reseedCheck"
  • type = "exclusive"
  • timeout = 1
  • throwOnTimeout = false
  • {
  •  
  • // Perform double-check - generator may have already been reseeded by
  • // a parallel request.
  • if ( now() >= reseedAt ) {
  •  
  • reseedAt = getNextReseedAt();
  •  
  • // NOTE: Once the generator was seeded internally, this re-seeding
  • // will only ever "add to" the existing seed. As such, this is still
  • // building on top of the original randomness and calling this, on
  • // interval, this will never reduce randomness.
  • generator.setSeed( generator.generateSeed( javaCast( "int", 32 ) ) );
  •  
  • }
  •  
  • } // END: Lock.
  •  
  • }
  •  
  • // Create the byte buffer into which the random bytes will be written. Since
  • // there's no "correct" way to generate a byte array in ColdFusion, we can
  • // generate a string of the desired length and then decode it into bytes.
  • var byteBuffer = charsetDecode( repeatString( " ", byteCount ), "utf-8" );
  •  
  • generator.nextBytes( byteBuffer );
  •  
  • return( encodeBytes( byteBuffer ) );
  •  
  • }
  •  
  •  
  • // ---
  • // PRIVATE METHODS.
  • // ---
  •  
  •  
  • /**
  • * I encode the given byte array using the base64url character-set.
  • *
  • * @bytes I am the byte array being encoded.
  • * @output false
  • */
  • private string function encodeBytes( required binary bytes ) {
  •  
  • var token = binaryEncode( bytes, "base64" );
  •  
  • // Replace the characters that are not allowed in the base64url format. The
  • // characters [+, /, =] are removed for URL-based base64 values because they
  • // have significant meaning in the context of URL paths and query-strings.
  • token = replace( token, "+", "-", "all" );
  • token = replace( token, "/", "_", "all" );
  • token = replace( token, "=", "", "all" );
  •  
  • return( token );
  •  
  • }
  •  
  •  
  • /**
  • * I calculate the next date of reseeding.
  • *
  • * @output false
  • */
  • private date function getNextReseedAt() {
  •  
  • return( dateAdd( "h", 1, now() ) );
  •  
  • }
  •  
  • }

After the SHA1PRNG generator is created and initialized, I'm also re-seeding it ever-so-often. This action does not make the data "more random." But, it does decrease the chances that an attacker would be able to calculate the original seed value based on the observation of a large number of generated values.

Once I have my TokenGenerator.cfc, I can instantiate it and generate some values:

  • <cfscript>
  •  
  • tokenGenerator = new TokenGenerator();
  •  
  • // Generate us some random tokens, for the win!
  • for ( i = 1 ; i <= 25 ; i++ ) {
  •  
  • writeOutput( "#i#: " & tokenGenerator.nextToken() & "<br />" );
  •  
  • }
  •  
  • </cfscript>

Running this code results in the following page output:

1: nmbePL_uQu1bXapssdhl4c1qr3u5Ml6NOmxfZdaeKJs
2: -aqKX7qAsCLRbATur9n3iL1E_lg0VSdMfJrvhzLkvVU
3: p2nxsk7NnlRXnqHjyRC70buuIpUqD2MG65u8DgYwMJA
4: UJ_KFDeMUDqrNLEnCmzF2FdyhkcRGSfhGe8r_craoJg
5: LUbpv_NbMzfWsH3Gu83b5QMDgr-lYyOeBmX5v_goWyE
6: 3rYfhsdpvHQ6nV_Re1TeTseJaGQkO0J-XV3cHoqH2u0
7: -gD9-pRFgtWXvpCnqfXwSKjxrib_3GWwOebQoWyU71Y
8: 86-fMVYHG1k7gEgeuSt25m5eAr9YPLKMQ754u9LPkNo
9: b3-udB2XxQyEx61IOmRdd1ZF0WnPVW0GcQ1NRKHoptY
10: OkFPKT_o1vUX5ajOTOsMDLqo5seudJ8qLG7tuJ9PVOk
11: iqtfgG9sbhaC5HyW6pMsyoIzz4Hg3SqBQVt9SkV7TwI
12: KDeBDPD8PTbYawV_5XaPz4S6m0tP4xhRdX9cn7bO1ww
13: A7jSHrnCQSu3_-8g4r84VNMzSxM5iLktp0sL7qUSL5o
14: FBY2PFKWjIpQvLzu0rnhozN2UFHPpFKbSzxzdrGOJ94
15: tDJ39crw1Dn9OfJGYLL706O8q98HlO3wga6SxUKkHKA
16: -9MgKXh671uXY3OSt7D9y6J16YtAXOOYIjck-0zsGyw
17: mkehI7xKJ1B-agtPEwdD9R_dh98bwICUvP9Bs79ey5Q
18: 6ZtFM7wkRLdciln3Ylel70YAqT0JCE9Ao_LTl7tgTB8
19: KBakVN5b5ZV4CXE6kkVBdwdSAAPQu3bDaOiDYgrGY7o
20: MJ8gac-loTG0-5bwPXTplb_XxXm8safXWm3ePRL7WWM
21: Glxjw_5l3wMpbhF5wOcaElPfF_D_DLwnoSfvsSYhJSw
22: WMJaoZJC20hnZ6mPn5WiemOLhym81ysFQB6KG67DHMk
23: Dol-8HM1rmKggNaNztw73WhBkZkxMklFTcicEFBuOp0
24: 4D6_cbe1YzZtM00TA7Xn4OssvHnf3jJtiA-3bRZ4FLo
25: YnPJ6-UNYYTO4W9zsB4GxcIVSgncSAYHwgcQDvMyzxs

Yay! Random tokens!

I won't offer up to much more explanation than this as I would quickly get out of my depth of understanding. But, if anyone with more security understanding wants to point out problems with this code, I would be super grateful to hear about it.




Reader Comments

@Henry,

Wow, that's a super interesting approach. To be honest, I 100% completely forgot that randRange() can even accept an algorithm as part of its invocation. Ironically, I did a google search and it turns out that I had experimented with randRange() years ago:

http://www.bennadel.com/blog/852-comparing-coldfusion-number-randomization-algorithms.htm

I think I immediately forgot that it was even an option :D I don't think I've ever actually used it that way.

But, going back to your approach, I can't speak to what the randRange() method is actually doing behind the scenes; but, philosophically speaking, it would appear to accomplish the same thing. Meaning, you are using a "secure" algorithm to generate a collection of bytes and then you're converting that bytes to a base64url value.

And, brownie points for using replaceList(). That's another one of those methods that I forgot even existed :D

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
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.