Argon2 Password Hashing With ColdFusion
Categorized as: Tech Tips

This article details how ColdFusion developers may easily store user passwords using the Argon2id password hashing algorithm as recommended by the Open Web Application Security Project (OWASP).
At the time of this writing, neither Adobe ColdFusion, nor Lucee support the ability to do this natively. However, being a Java application means ColdFusion can reach down into its underlying Java engine and do anything Java can do, only easier. Fortunately Java does support the Argon2id hashing algorithm thanks to Password4j from David Bertoldi.
Before going any further, I wish to point readers to, and thank, Ben Nadel for his fine article Using Password4j And The BCrypt, SCrypt, And Argon2 Password Hashing Algorithms In Lucee CFML 5.3.7.47. While his solution details a slightly different approach (it was developed using earlier versions of Lucee and Password4j than are available today), the insight gleaned from Ben’s article was invaluable in developing this solution.
What is Argon2
Argon2 is a hashing algorithm specifically developed for the storage of passwords and currently regarded as the preferred hashing algorithm for secure password storage. OWASP specifies in their Password Storage Cheat Sheet that passwords are to be stored using the Argon2id hashing algorithm with a minimum configuration of:
- 19 MiB of memory
- an iteration count of 2
- 1 degree of parallelism.
Password4j does not default to those settings, but the solution described here does while also providing developers a way to override those defaults if they choose. Password4j also supports other hashing algorithms such as BCrypt, SCrypt and PBKDF2. With minor modification, the code samples below could be altered to support those algorithms also, but because the recommendation from OWASP is for Argon2id, that is the exclusive focus of this solution.
Installing Password4j
“Installing” may be an overstatement of what we’re really doing here. All we really need to do is grab a couple .jar files and drop them into our ColdFusion or Lucee installation. To do this:
- Visit the Maven Repository.
- You will want to use the latest version available. At the time of this writing that is 1.8.4 so click the 1.8.4 link on the page to go to that version’s download page. (Sidenote: The folks behind the MVN Repository could certainly apply some user interface improvements to their entire site so you may have to scan the screen, but the links are there.)
- We’re looking for the .jar file. As stated above, if this is your first time doing this, it may not be readily obvious, so the easiest way to find the link to download the file we’re after may be to do a Ctrl + f and search for jar. Once you’ve found the link, click it to download to your browser’s download folder.
- But that’s not all. Scroll further down the page and you will find listed any compile dependencies which may be needed. For version 1.8.4, there is a single compile dependency which happens to be named slf4j-api-2.0.12.jar so click the necessary link to download that also.
- Now that you have all the files you’re after, move them from your browser’s download folder to your server’s jar container folder. The location of this folder could vary depending on whether you’re using ColdFusion or Lucee. If Lucee, it could further depend on whether you’re using multiple web-contexts or a single server-context. As an example, for a single server-context configuration of Lucee, the folder we’re interested in is called lib and may be found at {lucee}/lib. Drop the jar files in there so they will be right alongside any other jar files. Pro tip, I like to also create a readme file to include in this folder named something like, Password4j.txt which explains what the two additional jar files are.
- Restart the ColdFusion or Lucee service so that it will recognize the two newly-added jar files. Now you’re ready for some code.
Calling Password4j
To call Password4j I have created a ColdFusion component (CFC) as follows:
<cfcomponent
displayname="Hash password"
hint="Hashes plain-text strings using the Argon2id algorithm."
output="false">
<cffunction
name="hashing_object"
returntype="string"
access="private">
<!--- Parameters documented at https://github.com/Password4j/password4j/wiki/Argon2. --->
<cfargument name="memory" required="false" type="numeric" default="19456">
<cfargument name="iterations" required="false" type="numeric" default="2">
<cfargument name="parallelisation" required="false" type="numeric" default="1">
<cfargument name="output_length" required="false" type="numeric" default="32">
<cfargument name="version" required="false" type="numeric" default="19">
<!--- Allowable values are ID, I, or D. ID is recommended. --->
<cfset VARIABLES.argon2_enum = createObject("java", "com.password4j.types.Argon2").valueOf("ID")>
<cfset VARIABLES.hashing_object = createObject("java", "com.password4j.Argon2Function").getInstance(
ARGUMENTS.memory,
ARGUMENTS.iterations,
ARGUMENTS.parallelisation,
ARGUMENTS.output_length,
VARIABLES.argon2_enum,
ARGUMENTS.version
)>
<cfreturn VARIABLES.hashing_object>
</cffunction>
<cffunction
name="hash_password"
returntype="string"
access="public">
<cfargument name="password" required="true" type="string">
<cfargument name="memory" required="false" type="numeric" default="19456">
<cfargument name="iterations" required="false" type="numeric" default="2">
<cfargument name="parallelisation" required="false" type="numeric" default="1">
<cfargument name="output_length" required="false" type="numeric" default="32">
<cfargument name="version" required="false" type="numeric" default="19">
<!--- password4j documented at: https://password4j.com/ --->
<!--- Load the Java class. --->
<cfset VARIABLES.password = createObject("java", "com.password4j.Password")>
<!--- Create a hash using Argon2id. --->
<cfset VARIABLES.hash = VARIABLES.password
.hash(ARGUMENTS.password)
.addRandomSalt(16)
.with( hashing_object(
ARGUMENTS.memory,
ARGUMENTS.iterations,
ARGUMENTS.parallelisation,
ARGUMENTS.output_length,
ARGUMENTS.version
)
)
.getResult()>
<cfreturn VARIABLES.hash>
</cffunction>
<cffunction
name="verify"
returntype="boolean"
access="public">
<cfargument name="password_from_user" required="true" type="string">
<cfargument name="password_from_db" required="true" type="string">
<cfargument name="memory" required="false" type="numeric" default="19456">
<cfargument name="iterations" required="false" type="numeric" default="2">
<cfargument name="parallelism" required="false" type="numeric" default="1">
<cfargument name="output_length" required="false" type="numeric" default="32">
<cfargument name="version" required="false" type="numeric" default="19">
<!--- Password4j documented at: https://password4j.com/ --->
<!--- Load the Java class. --->
<cfset VARIABLES.password = createObject("java", "com.password4j.Password")>
<!--- Compare the user-provided password to that stored in the database. --->
<cfset VARIABLES.verify = VARIABLES.password
.check(ARGUMENTS.password_from_user, ARGUMENTS.password_from_db)
.with( hashing_object(
ARGUMENTS.memory,
ARGUMENTS.iterations,
ARGUMENTS.parallelism,
ARGUMENTS.output_length,
ARGUMENTS.version
)
)>
<cfreturn VARIABLES.verify>
</cffunction>
<cffunction
name="verify_and_update"
returntype="struct"
access="public">
<cfargument name="password_from_user" required="true" type="string">
<cfargument name="password_from_db" required="true" type="string">
<!--- Get parameters associated with the current hash string . --->
<cfset VARIABLES.actual_hash_params = createObject("java", "com.password4j.Argon2Function")
.getInstanceFromHash(ARGUMENTS.password_from_db)>
<!--- Get parameters associated with the desired hash string . --->
<cfset VARIABLES.desired_hash_params = hashing_object()>
<cfset VARIABLES.password = createObject("java", "com.password4j.Password")>
<cfset VARIABLES.verify_and_update = VARIABLES.password
.check(ARGUMENTS.password_from_user, ARGUMENTS.password_from_db)
.andUpdate()
.addNewRandomSalt(16)
.with( VARIABLES.actual_hash_params, VARIABLES.desired_hash_params )>
<cfset VARIABLES.response = StructNew()>
<cfset VARIABLES.response.isVerified = VARIABLES.verify_and_update.isVerified()>
<!---
isUpdated() is not currently supported in any useful way.
It is provided here solely in the interest of preserving awareness
of its existence. In some later release of Password4J it may perhaps
have some reliable value.
--->
<cfset VARIABLES.response.isUpdated = VARIABLES.verify_and_update.isUpdated()>
<cfset VARIABLES.response.hash = VARIABLES.verify_and_update.getHash().getResult()>
<cfreturn VARIABLES.response>
</cffunction>
</cfcomponent>
The Hashing Object
Within this component is a function named hashing_object. That function has five arguments associated with it. For a further explanation of what those arguments are, visit the Argon2 GitHub page where we see the six Argon2 parameters and their default values. The difference of one between what is listed at GitHub and the arguments in the function above may be accounted for by the fact that the function above is hardcoded to reference using the Argon2id hashing algorithm—as per the OWASP recommendation—and does not allow for specifying an alternative.
Note also that the default values listed for Argon2 are nearly identical to what is listed as the default values inside hashing_object with one key difference, memory. Argon2 defaults to 15 kibibytes (KiB). OWASP specifies a minimum of 19 mebibytes (MiB). 19 MiB equals 19456 KiB and because Argon2 measures memory in KiB, 19456 is specified as the default value in the hashing_object.
The purpose of hashing_object is not to actually return a hashed password. It exists solely to accommodate the ability to specify our own values for the Argon2 parameters. You will see it referenced in other functions within the CFC.
You will also notice that none of the arguments are required. By simply allowing the default values to take effect, you will instantly be following the current (at the time of this writing) OWASP recommendations. However, by using named parameters in your function call, you may override any, or all, of those default values.
The Hashing Password Function
Moving on to the hash_password function, this is where a plain-text string is passed in and an Argon2id hashed value is returned. Notice that this is where hashing_object comes into use as hash_password makes a call to it. An example of calling this function, while also specifying a custom parameter, could look like:
<cfinvoke
component="password-hashing"
method="hash_password"
returnvariable="response">
<cfinvokeargument name="password" value="a-user-provided-password">
<cfinvokeargument name="iterations" value="5">
</cfinvoke>
<cfoutput>#response#</cfoutput>
The only required parameter for hash_password is the password itself, but in the example above, the iterations argument is also specified with a value of 5, overriding the default of 2.
The Verify Function
Next we get to the function named verify. As the name implies, this function verifies whether or not a user-supplied password matches what is already stored inside a password storage database. It requires two arguments:
- password_from_user which is provided by the user. Perhaps through a login form.
- password_from_db which would need to be read from a database for any given user.
When the hash of password_from_user matches password_from_db the function returns true as a boolean response and you may log in the user.
Calling the verify function might look like:
<cfinvoke
component="password-hashing"
method="verify"
returnvariable="response">
<cfinvokeargument name="password_from_user" value="a-user-provided-password">
<cfinvokeargument name="password_from_db" value="a-password-read-from-a-database">
</cfinvoke>
<cfoutput>#response#</cfoutput>
While only two arguments are required for verify, it accepts several others. Honestly those others should have little use considering that we’re only comparing two values and not really creating anything new that might need to accept custom arguments. The optional parameters are included mostly for future-proofing in case one should find oneself in a difficult-to-foresee scenario requiring custom values to be passed for those arguments, but for most use cases they can be safely ignored.
A Bonus Function
There is a bonus function included in the CFC that you may, or may not, have use for. Let me explain. Suppose you have a collection of passwords that were previously hashed according to generally-accepted recommendations at the time they were created. At some later date those recommendations were updated to become more stringent and you wish to adopt those new recommendations. Wouldn’t it be wonderful if when a user logs in, you could both verify the password submitted by the user and also inspect its properties to determine if they match those of your newly adopted hashing algorithm? If so, great, processing proceeds as normal. If not, a new hash value is created which you can capture and use to update the hash value in the database. Do some googling and you might get an AI-generated response that mentions an isUpdated method inside Password4j that does exactly that. However, nowhere in the official documentation for Password4j is the isUpdated method mentioned. It does exist, but its use is not officially documented and it does not behave in a way that accommodates the scenario described above. What is documented and possible to do is to use the check() method in conjunction with the addUpdate() method as shown in the verify_and_update function above. Doing so will both verify if the user-submitted password matches what is already on record for a user and also generate a new hash value using your more current hashing properties. What you do with that new hash value is up to you. You may not need to do anything with it at all if you can be certain that your stored password hash values are already using the desired hashing algorithm and associated properties. In which case using the verify, rather than the verify_and_update function would make more sense.
My recommendation is this. When the time comes to update hash values, add a field in your database right alongside the password hash. Name this field something like needsUpating and set its default value to 1. Use that value in your application code to signal that a user’s password needs to be rehashed the next time they log in. That is when the verify_and_update function could prove useful as it avoids making two separate function calls: one to the verify function and then a second to the hash_password function. When you do update a user’s password, simultaneously set the needsUpdating field to 0 so that going forward your application knows to bypass any further password updates. Or at least until the next time when it’s necessary to update hashes again.
Calling verify_and_update looks almost identical to calling verify and might look like:
<cfinvoke
component="password-hashing"
method="verify_and_update"
returnvariable="response">
<cfinvokeargument name="password_from_user" value="a-user-provided-password">
<cfinvokeargument name="password_from_db" value="a-password-read-from-a-database">
</cfinvoke>
<cfdump var="#response#">