Skip to content

Instantly share code, notes, and snippets.

@bennadel
Created May 29, 2021 20:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bennadel/da40014f0a6f3e094b8c62b0eae4c915 to your computer and use it in GitHub Desktop.
Save bennadel/da40014f0a6f3e094b8c62b0eae4c915 to your computer and use it in GitHub Desktop.
Using Password4j And The BCrypt, SCrypt, And Argon2 Password Hashing Algorithms In Lucee CFML 5.3.7.47
<cfscript>
// Read-in the file generated by Lucee CFML (with Password4j).
data = deserializeJson( fileRead( expandPath( "./interop.json" ) ) );
for ( test in data ) {
switch ( test.algorithm ) {
case "bcrypt":
input = test.input;
hashedInput = test.hashedInput;
writeDump( verifyBcryptHash( input, hashedInput ) );
break;
case "scrypt":
input = test.input;
// CAUTION: I have to remove "$s0" to get Adobe ColdFusion 2021 to like
// the hash generated by the Password4j scrypt function.
hashedInput = test
.hashedInput
.reReplace( "^\$s0", "" )
;
writeDump( verifyScryptHash( input, hashedInput ) );
break;
}
}
</cfscript>
<cfscript>
input = "Kablamo$auce";
data = [
{
algorithm: "bcrypt",
input: input,
hashedInput: generateBcryptHash( input )
},
{
algorithm: "scrypt",
input: input,
hashedInput: generateScryptHash( input )
}
];
fileWrite( expandPath( "./interop.json" ), serializeJson( data ) );
</cfscript>
<cfscript>
password = new Password([
"./lib/password4j/commons-lang3-3.12.0.jar",
"./lib/password4j/password4j-1.5.3.jar",
"./lib/password4j/slf4j-api-1.7.30.jar",
"./lib/password4j/slf4j-nop-1.7.30.jar"
]);
// Read-in the file generated by Adobe ColdFusion 2021 built-in functions.
data = deserializeJson( fileRead( "./interop.json" ) );
for ( test in data ) {
switch ( test.algorithm ) {
case "bcrypt":
input = test.input;
hashedInput = test.hashedInput;
dump( password.bcryptHashVerify( input, hashedInput ) );
break;
case "scrypt":
input = test.input;
// CAUTION: I have to prepend "$s0" to get Password4j to like the hash
// generated by the Adobe ColdFusion 2021 scrypt function.
hashedInput = ( "$s0" & test.hashedInput );
dump( password.scryptHashVerify( input, hashedInput ) );
break;
}
}
</cfscript>
<cfscript>
password = new Password([
"./lib/password4j/commons-lang3-3.12.0.jar",
"./lib/password4j/password4j-1.5.3.jar",
"./lib/password4j/slf4j-api-1.7.30.jar",
"./lib/password4j/slf4j-nop-1.7.30.jar"
]);
// Configure Password4j defaults to use the same settings that Adobe ColdFusion 2021
// generateBcryptHash() and generateScryptHash() functions will use if we don't pass-
// in any additional options.
password
.withBcryptDefaults(
version = "a",
costFactor = 10
)
.withScryptDefaults(
// CAUTION: The Adobe ColdFusion 2021 documentation says that the default
// work factor it "16,348", which is the WRONG output of (2^14).
workFactor = 16384,
resources = 8,
parallelisation = 1,
outputLength = 32,
saltLength = 8
)
;
input = "Kablamo$auce";
// When generating the Lucee CFML outputs, we'll just use the Password4j defaults.
data = [
{
algorithm: "bcrypt",
input: input,
hashedInput: password.bcryptHashGet( input )
},
{
algorithm: "scrypt",
input: input,
hashedInput: password.scryptHashGet( input )
}
];
fileWrite( expandPath( "./interop.json" ), serializeJson( data ) );
</cfscript>
/**
* I provide a Lucee CFML wrapper around the Password4j Java library.
*
* GitHub: https://github.com/Password4j/password4j
* Maven: https://mvnrepository.com/artifact/com.password4j/password4j
*/
component
output = false
hint = "I provide password hashing and verification functions (using Password4j)."
{
variables.defaults = {
// These properties are described here:
// - https://github.com/Password4j/password4j/wiki/Argon2
argon2: {
memory: 12,
iterations: 20,
parallelisation: 2,
ouputLength: 32,
type: "Argon2id",
version: 19
},
// These properties are described here:
// - https://github.com/Password4j/password4j/wiki/BCrypt
bcrypt: {
version: "B",
costFactor: 10
},
// These properties are described here:
// - https://github.com/Password4j/password4j/wiki/Scrypt
scrypt: {
workFactor: 32768,
resources: 8,
parallelisation: 1,
outputLength: 64,
// Added for Adobe ColdFusion 2021 interoperability.
saltLength: 8
}
};
/**
* I initialize the password component with the given Password4j JAR paths.
*/
public void function init( required array jarPaths ) {
variables.jarPaths = arguments.jarPaths;
}
// ---
// PUBLIC METHODS.
// ---
/**
* I generate an Argon2 hash of the given input using the given characteristics. All
* characteristics are optional; and, any not provided will use the defined defaults.
*/
public string function argon2HashGet(
required string input,
numeric memory = defaults.argon2.memory,
numeric iterations = defaults.argon2.iterations,
numeric parallelisation = defaults.argon2.parallelisation,
numeric ouputLength = defaults.argon2.ouputLength,
string type = defaults.argon2.type,
numeric version = defaults.argon2.version
) {
var enum = javaNew( "com.password4j.types.Argon2" );
switch ( type ) {
case "Argon2d":
case "Argon2i":
case "Argon2id":
var enumToken = type.listRest( "2" ).ucase();
var enumType = enum[ enumToken ];
break;
default:
throw(
type = "InvalidArgon2Type",
message = "Valid argon2 types are: Argon2d, Argon2i, Argon2id (recommended)",
detail = "Provided type: #type#"
);
break;
}
var hashingFunction = javaNew( "com.password4j.Argon2Function" )
.getInstance( memory, iterations, parallelisation, ouputLength, enumType, version )
;
var hashedInput = javaNew( "com.password4j.Password" )
.hash( input )
.with( hashingFunction )
.getResult()
;
return( hashedInput );
}
/**
* I verify the Argon2 hash of the given input against the expected hash.
*
* NOTE: All hash-algorithm characteristics will be pulled directly out of the
* expected hash. As such, they do not need to be provided as arguments.
*/
public boolean function argon2HashVerify(
required string input,
required string hashedInput
) {
var hashingFunction = javaNew( "com.password4j.Argon2Function" )
.getInstanceFromHash( hashedInput )
;
var isVerified = javaNew( "com.password4j.Password" )
.check( input, hashedInput )
.with( hashingFunction )
;
return( isVerified );
}
/**
* I generate a BCrypt hash of the given input using the given characteristics. All
* characteristics are optional; and, any not provided will use the defined defaults.
*/
public string function bcryptHashGet(
required string input,
string version = variables.defaults.bcrypt.version,
numeric costFactor = variables.defaults.bcrypt.costFactor
) {
var enum = javaNew( "com.password4j.types.BCrypt" );
switch ( version ) {
case "a":
case "b":
case "x":
case "y":
var enumToken = version.ucase();
var enumVersion = enum[ enumToken ];
break;
default:
throw(
type = "InvalidBcryptVersion",
message = "Valid bcrypt versions are: a, b (recommended), x, y",
detail = "Provided version: #version#"
);
break;
}
var hashingFunction = javaNew( "com.password4j.BCryptFunction" )
.getInstance( enumVersion, costFactor )
;
var hashedInput = javaNew( "com.password4j.Password" )
.hash( input )
.with( hashingFunction )
.getResult()
;
return( hashedInput );
}
/**
* I verify the BCrypt hash of the given input against the expected hash.
*
* NOTE: All hash-algorithm characteristics will be pulled directly out of the
* expected hash. As such, they do not need to be provided as arguments.
*/
public boolean function bcryptHashVerify(
required string input,
required string hashedInput
) {
var hashingFunction = javaNew( "com.password4j.BCryptFunction" )
.getInstanceFromHash( hashedInput )
;
var isVerified = javaNew( "com.password4j.Password" )
.check( input, hashedInput )
.with( hashingFunction )
;
return( isVerified );
}
/**
* I generate a SCrypt hash of the given input using the given characteristics. All
* characteristics are optional; and, any not provided will use the defined defaults.
*/
public string function scryptHashGet(
required string input,
numeric workFactor = defaults.scrypt.workFactor,
numeric resources = defaults.scrypt.resources,
numeric parallelisation = defaults.scrypt.parallelisation,
numeric outputLength = defaults.scrypt.outputLength,
numeric saltLength = defaults.scrypt.saltLength
) {
var hashingFunction = javaNew( "com.password4j.SCryptFunction" )
.getInstance( workFactor, resources, parallelisation, outputLength )
;
var hashedInput = javaNew( "com.password4j.Password" )
.hash( input )
.addRandomSalt( saltLength )
.with( hashingFunction )
.getResult()
;
return( hashedInput );
}
/**
* I verify the SCrypt hash of the given input against the expected hash.
*
* NOTE: All hash-algorithm characteristics will be pulled directly out of the
* expected hash. As such, they do not need to be provided as arguments.
*/
public boolean function scryptHashVerify(
required string input,
required string hashedInput
) {
var hashingFunction = javaNew( "com.password4j.SCryptFunction" )
.getInstanceFromHash( hashedInput )
;
var isVerified = javaNew( "com.password4j.Password" )
.check( input, hashedInput )
.with( hashingFunction )
;
return( isVerified );
}
/**
* I update the default arguments for the Argon2 hashing method.
*/
public any function withArgon2Defaults(
numeric memory = defaults.argon2.memory,
numeric iterations = defaults.argon2.iterations,
numeric parallelisation = defaults.argon2.parallelisation,
numeric ouputLength = defaults.argon2.ouputLength,
string type = defaults.argon2.type,
numeric version = defaults.argon2.version
) {
defaults.argon2.memory = memory;
defaults.argon2.iterations = iterations;
defaults.argon2.parallelisation = parallelisation;
defaults.argon2.ouputLength = ouputLength;
defaults.argon2.type = type;
defaults.argon2.version = version;
return( this );
}
/**
* I update the default arguments for the BCrypt hashing method.
*/
public any function withBCryptDefaults(
string version = defaults.bcrypt.version,
string costFactor = defaults.bcrypt.costFactor
) {
defaults.bcrypt.version = version;
defaults.bcrypt.costFactor = costFactor;
return( this );
}
/**
* I update the default arguments for the SCrypt hashing method.
*/
public any function withSCryptDefaults(
numeric workFactor = defaults.scrypt.workFactor,
numeric resources = defaults.scrypt.resources,
numeric parallelisation = defaults.scrypt.parallelisation,
numeric outputLength = defaults.scrypt.outputLength,
numeric saltLength = defaults.scrypt.saltLength
) {
defaults.scrypt.workFactor = workFactor;
defaults.scrypt.resources = resources;
defaults.scrypt.parallelisation = parallelisation;
defaults.scrypt.outputLength = outputLength;
defaults.scrypt.saltLength = saltLength;
return( this );
}
// ---
// PRIVATE METHODS.
// ---
/**
* I create the given Java class using the Password4j JAR paths.
*/
private any function javaNew( required string className ) {
return( createObject( "java", className, jarPaths ) );
}
}
String hashedInput = Password
.hash( plainTextPassword )
.withBcrypt()
.getResult()
;
<cfscript>
// Create our Password4j wrapper.
// --
// NOTE: One of the coolest features of Lucee CFML is the fact that we can load Java
// classes directly from a set of JAR files. I've downloaded the Password4j v1.5.3
// files from the Maven repository:
// - https://mvnrepository.com/artifact/com.password4j/password4j
password = new Password([
"./lib/password4j/commons-lang3-3.12.0.jar",
"./lib/password4j/password4j-1.5.3.jar",
"./lib/password4j/slf4j-api-1.7.30.jar",
"./lib/password4j/slf4j-nop-1.7.30.jar"
]);
myPassword = "Ca$hRulezEverythingAroundM3!";
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
timer
label = "Testing BCrypt with defaults"
type = "outline"
{
hashedPassword = password.bcryptHashGet( myPassword );
dump( myPassword );
dump( hashedPassword );
dump( hashedPassword.len() );
dump( password.bcryptHashVerify( myPassword, hashedPassword ) );
}
echo( "<hr />" );
timer
label = "Testing SCrypt with defaults"
type = "outline"
{
hashedPassword = password.scryptHashGet( myPassword );
dump( myPassword );
dump( hashedPassword );
dump( hashedPassword.len() );
dump( password.scryptHashVerify( myPassword, hashedPassword ) );
}
echo( "<hr />" );
timer
label = "Testing Argon2 with defaults"
type = "outline"
{
hashedPassword = password.argon2HashGet( myPassword );
dump( myPassword );
dump( hashedPassword );
dump( hashedPassword.len() );
dump( password.argon2HashVerify( myPassword, hashedPassword ) );
}
</cfscript>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment