A couple of years ago, I built a ColdFusion port of the CUID library which we’ve been using successfully at InVision. The CUID library provides collision-resistant IDs that are optimized for horizontal scaling and performance. Just recently, however, Eric Elliott released Cuid2 – an updated version of the library intended to address some philosophical security issues. I wanted to create a ColdFusion port of his new Cuid2 library.
View this code in my CUID2 For ColdFUsion project on GitHub.
The CUID2 token is guaranteed to start with a letter and be of a consistent, configured length. The values within the token consist of the Base36 character set, which is [a-z0-9]
. The ColdFusion port of CUID2 is thread safe and can be cached and reused within your ColdFusion application:
<cfscript>
// Cachced reference to the CUID instance.
cuid2 = new lib.Cuid2();
writeDump({ token: cuid2.createCuid() });
writeDump({ token: cuid2.createCuid() });
writeDump({ token: cuid2.createCuid() });
writeDump({ token: cuid2.createCuid() });
</cfscript>
Running this ColdFusion code gives us the following output:
token: uem955pnse56id49y6bcmjz8
token: ek9lgqi0mfkh9wmxnb6rvzuc
token: lycfyvl0dlspi0us6smqkkr0
token: x0hhypk7l7k4hga8newn4gnw
The Cuid2.cfc
ColdFusion component can be instantiated with two optional arguments:
new Cuid2( [ length [, fingerprint ] ] )
length
– The desired size of the generated tokens. This defaults to 24 but can be anything between 24 and 32 (inclusive).fingerprint
– An additional source of entropy associated with the device. This will default to the name of the JVM process as provided by theManagementFactory
Runtime MX Bean.
Under the hood, the randomness is provided by ColdFusion’s built-in function, randRange()
, which is being executed with the sha1prng
algorithm for pseudo-random number generation. We can perform a high-level test of the randomness by generating 1,000,000 tokens and then seeing how well these values get distributed into a set of buckets:
<cfscript>
cfsetting( requestTimeout = 300 );
length = 24;
cuid = new lib.Cuid2( length );
count = 1000000;
tokens = [:];
buckets = [:];
BigIntegerClass = createObject( "java", "java.math.BigInteger" );
// Create buckets for our distribution counters. As each CUID is generated, it will be
// sorted into one of these buckets (used to increment the given counter).
for ( i = 1 ; i <= 20 ; i++ ) {
buckets[ i ] = 0;
}
bucketCount = BigIntegerClass.init( buckets.count() );
startedAt = getTickCount();
for ( i = 1 ; i <= count ; i++ ) {
token = cuid.createCuid();
// Make sure the CUID is generated at the configured length.
if ( token.len() != length ) {
throw(
type = "Cuid2.Test.InvalidTokenLength",
message = "Cuid token length did not match configuration.",
detail = "Length: [#length#], Token: [#token#]"
);
}
tokens[ token ] = i;
// If the CUIDs are all unique, then each token should represent a unique entry
// within the struct. But, if there is a collision, then a key will be written
// twice and the size of the struct will no longer match the current iteration.
if ( tokens.count() != i ) {
throw(
type = "Cuid2.Test.TokenCollision",
message = "Cuid token collision detected in test.",
detail = "Iteration: [#numberFormat( i )#]"
);
}
// Each token is in the form of ("letter" + "base36 value"). As such, we can strip
// off the leading letter and then use the remaining base36 value to generate a
// BigInteger instance.
intRepresentation = BigIntegerClass.init( token.right( -1 ), 36 );
// And, from said BigInteger instance, we can use the modulo operator to sort it
// into the relevant bucket / counter.
bucketIndex = ( intRepresentation.remainder( bucketCount ) + 1 );
buckets[ bucketIndex ]++;
}
</cfscript>
<cfoutput>
<p>
Tokens generated: #numberFormat( count )# (no collisions)<br />
Time: #numberFormat( getTickCount() - startedAt )#ms
</p>
<ol>
<cfloop item="key" collection="#buckets#">
<li>
<div class="bar" style="width: #fix( buckets[ key ] / 100 )#px ;">
#numberFormat( buckets[ key ] )#
</div>
</li>
</cfloop>
</ol>
<style type="text/css">
.bar {
background-color: ##ff007f ;
border-radius: 3px 3px 3px 3px ;
color: ##ffffff ;
margin: 2px 0px ;
padding: 2px 5px ;
}
</style>
</cfoutput>
Since the CUID2 token is, essentially, a Base36-encoded value, we can decode that value back into a byte array; and then, use that byte array to initialize a BigInteger
class. And, once we have a numeric representation of the CUID2 token, we can use the modulo operator to sort it into a given bucket. Assuming we have 20 buckets, you can think of this operation like:
asBigInt( token ) % 20 => bucket
Now, when we run the above ColdFusion code, we get the following output:
As you can see, the 1,000,000 CUID2 tokens are, roughly, evenly distributed across the 20 buckets. And, in those million keys, no collisions were detected.
I would not necessarily consider my CUID2 port as production ready since no one else has looked at it and I haven’t tried running it in production. But, if you are looking for a CUID2 port in ColdFusion, hopefully this will at least point you in the right direction.
In Eric Elliott’s implementation, he uses the SHA3
family of hashing algorithms in order to reduce the sources of entropy down into a single value. SHA3
wasn’t introduced to Java until version 9. As such, I’m using the SHA-256
hashing algorithm. To be honest, I don’t know what kind of impact this has on the token (or on the security of the token).
Part of the security issue that CUID2 is addressing (over CUID) is to remove any sense of sorting / increasing order. When an ID – of any kind – is generated in order, the order “leaks information” about the token. Since CUID2 has no predictable order, it is inherently more secure. Elliott talks about the security considerations more in the CUID2 repository.
Want to use code from this post?
Check out the license.