From d5d1092baade1b69b651c6fa9fdcec5793785e88 Mon Sep 17 00:00:00 2001 From: Ben Nadel Date: Thu, 26 Jan 2023 06:36:33 -0500 Subject: [PATCH] Default to SHA3, fallback to SHA2. This update allows the hashing algorithm to be configured. It defaults to `sha3-256`, which is the CUID2 specified algorithm. But, since `SHA3` isn't supported on Java 8, the hashing algorithm can be explicitly set to `sha-256` as a needed stop-gap. --- README.md | 14 +++----------- lib/Cuid2.cfc | 44 +++++++++++++++++++++++++++++++++++-------- tests/server-acf.json | 5 ++++- tests/server.json | 1 + 4 files changed, 44 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index c7c663f..801ca63 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,6 @@ by [Ben Nadel][ben-nadel] -## WARNING: Incomplete Implementation! - -I'm told that using `SHA-256` instead of `SHA3-256` (introduced in Java 9) is a disqualifying choice for the implementation due to the security properties of the earlier hashing algorithms. As such, please consider this implementation _incomplete_ and not production ready. - ----- - This is a **ColdFusion / CFML port** of the [Cuid2][cuid2] token generator created by [Eric Elliott][eric-elliott]. Cuid2 is an evolution of the [Cuid][cuid] library (for which I also have a [ColdFusion port][ben-nadel-cuid]) that is intended to address some security issues. Each Cuid token starts with a letter and is a consistent, configured length between 24 (default) and 34 characters. @@ -38,17 +32,15 @@ token: lycfyvl0dlspi0us6smqkkr0 token: x0hhypk7l7k4hga8newn4gnw ``` -The `Cuid2.cfc` ColdFusion component can be instantiated with two optional arguments: +The `Cuid2.cfc` ColdFusion component can be instantiated with three optional arguments: -`new Cuid2( [ length [, fingerprint ] ] )` +`new Cuid2( [ length [, fingerprint [, algorithm ] ] ] )` * `length` - Numeric: The length of the generated token. Defaults to 24 but can be anything between 24 and 32. * `fingerprint` - String: The machine fingerprint. This is provided as an additional source of entropy. It defaults to the name of the JVM process as reported by the `ManagementFactory` Runtime MX Bean. -## Known Issues - -Eric Elliott uses the `SHA3-256` hashing algorithm in order to reduce the various sources of entropy down into a single token. Unfortunately, the `SHA3` algorithms weren't available in Java until version 9. As such, I'm using the `SHA-256` hashing algorithm. I don't know what kind of impact this will have on the security; but, I believe the `SHA-256` algorithm to still be a commonly used and secure algorithm. I could always make this a configurable property of the ColdFusion component. +* `algorithm` - String: The hash algorithm to be used when reducing the sources of entropy. It defaults to `SHA3-256` (which is the CUID2 standard); but, can also be set to `SHA-256` for older versions of Java (8) that don't support `SHA3` yet. ## Random Distribution diff --git a/lib/Cuid2.cfc b/lib/Cuid2.cfc index 79e3e00..4f7fa44 100644 --- a/lib/Cuid2.cfc +++ b/lib/Cuid2.cfc @@ -8,12 +8,16 @@ component hint = "I provide secure, collision-resistant ids optimized for horizontal scaling and performance." { + this.SHA3_256 = "sha3-256"; + this.SHA_256 = "sha-256"; + /** * I initialize the CUID2 generator with the given (optional) parameters. */ public void function init( numeric length, - string fingerprint + string fingerprint, + string algorithm ) { variables.minCuidLength = 24; @@ -42,11 +46,9 @@ component variables.MessageDigestClass = createObject( "java", "java.security.MessageDigest" ); // Store and test arguments. - // -- - // CAUTION: These assignments must come last because generating the fingerprint - // depends on other variables being defined. variables.cuidLength = testCuidLength( arguments.length ?: minCuidLength ); variables.processFingerprint = testProcessFingerprint( arguments.fingerprint ?: generateFingerprint() ); + variables.hashAlgorithm = testHashAlgorithm( arguments.algorithm ?: this.SHA3_256 ); } @@ -74,7 +76,7 @@ component // --- /** - * I generate a seucre random binary value of the given length. + * I generate a secure random binary value of the given length. */ private array function generateBytes( required numeric length ) { @@ -91,7 +93,7 @@ component /** - * I generate the devince fingerprint for the CUID. + * I generate the device fingerprint for the CUID. * * DIVERGENCE FROM CUID v1: In first version of CUID, the fingerprint generation was * guaranteed to be 4-characters. However, in CUID v2, the fingerprint is nothing more @@ -116,11 +118,11 @@ component */ private string function generateHashBlock() { - var inputs = MessageDigestClass.getInstance( "sha-256" ); + var inputs = MessageDigestClass.getInstance( hashAlgorithm ); // These are all just sources of entropy to aide in collision prevention. None of // the individual parts holds any particular magical meaning. - inputs.update( generateBytes( cuidLength * 2 ) ); + inputs.update( generateBytes( maxCuidLength * 2 ) ); inputs.update( charsetDecode( getTickCount(), "utf-8" ) ); inputs.update( charsetDecode( counter.getAndIncrement(), "utf-8" ) ); inputs.update( charsetDecode( processFingerprint, "utf-8" ) ); @@ -187,6 +189,32 @@ component } + /** + * I test and return the given hash algorithm, throwing an error if the algorithm is + * not supported. + */ + private string function testHashAlgorithm( required string value ) { + + value = value.lcase(); + + if ( + ( value != this.SHA3_256 ) && + ( value != this.SHA_256 ) + ) { + + throw( + type = "Cuid2.Algorithm.NotSupported", + message = "Cuid2 hashing algorithm not supported.", + detail = "At this time, only the following hashing algorithms are supported: [#this.SHA3_256#, #this.SHA_256#]." + ); + + } + + return( value ); + + } + + /** * I test and return the given fingerprint, throwing an error if the fingerprint is * invalid. diff --git a/tests/server-acf.json b/tests/server-acf.json index 580dd96..0ce7876 100644 --- a/tests/server-acf.json +++ b/tests/server-acf.json @@ -2,5 +2,8 @@ "app":{ "cfengine":"adobe@2021" }, - "name":"cuid-v2-acf" + "name":"cuid-v2-acf", + "jvm":{ + "javaVersion":"openjdk11" + } } diff --git a/tests/server.json b/tests/server.json index da02a0a..ed81406 100644 --- a/tests/server.json +++ b/tests/server.json @@ -4,6 +4,7 @@ }, "name":"cuid-v2-lucee", "jvm":{ + "javaVersion":"openjdk11", "args":"-Dlucee.preserve.case=true" } }