What is the WildFly (8.2) way to work with randomly salted passwords stored in a database?
Is an implementation of org.jboss.crypto.digest.DigestCallback
(in the password validation process) meant to have access to the salt part from the database?
Or should I simply hash and salt passwords by my self before handing them over to the login
method of HttpServletRequest
?
It looks to me like the 'WildFly way' to deal with passwords is to do what most containers do and deliver a non-secure solution out-of-the-box. I don't know why, but every standard JDBC realm implementation I've seen so far just hashes the passwords without salt... Which is totally insecure.
Open Source solution
EDIT: I found an out-of-the box solution that works on WildFly. I ended up using it myself and it works well. I can recommend it:
m9aertner/PBKDF2
Here is how I configured it:
First add a module to WildFly by creating a folder below modules/
, like this:
C:\WildFly\v8.2.0\modules\de\rtner\PBKDF2\main
Place the PBKDF2-1.1.0.jar
file in it along with a module.xml
with these contents:
<?xml version="1.0" encoding="UTF-8"?>
<module xmlns="urn:jboss:module:1.1" name="de.rtner.PBKDF2">
<resources>
<resource-root path="PBKDF2-1.1.0.jar"/>
</resources>
<dependencies>
<module name="org.picketbox"/>
<module name="javax.api"/>
</dependencies>
</module>`
Then, add a realm configuration to standalone.xml
:
<subsystem xmlns="urn:jboss:domain:security:1.2">
<security-domains>
<!-- .... -->
<security-domain name="MyRealm">
<authentication>
<login-module code="de.rtner.security.auth.spi.SaltedDatabaseServerLoginModule" flag="required" module="de.rtner.PBKDF2">
<module-option name="dsJndiName" value="java:/jdbc/MyDS"/>
<module-option name="principalsQuery" value="SELECT password FROM users WHERE username = ?"/>
<module-option name="rolesQuery" value="SELECT roles.name AS groupid, 'Roles' FROM roles INNER JOIN user_roles ON roles.name = users.username WHERE users.username = ?"/>
<module-option name="unauthenticatedIdentity" value="guest"/>
<!-- DEFAULT HASHING OPTIONS:
<module-option name="hmacAlgorithm" value="HMacSHA1" />
<module-option name="hashCharset" value="UTF-8" />
<module-option name="formatter" value="de.rtner.security.auth.spi.PBKDF2HexFormatter" />
<module-option name="engine" value="de.rtner.security.auth.spi.PBKDF2Engine" />
<module-option name="engine-parameters" value="de.rtner.security.auth.spi.PBKDF2Parameters" />
-->
</login-module>
</authentication>
</security-domain>
<!-- .... -->
</security-domains>
</subsystem>
The SQL queries are the same as for the standard DatabaseLoginModule
. The default hashing options need not be specified (as they are default) but you do need to be aware of them (and set them correctly) when creating new users in order to hash their password correctly with the same parameters.
Example use
Here is what I am doing in my code to create a new password hash (including salt) based on a given plaintext:
public static String hash(String plainText, String storedPassword) {
if (plainText == null) return null;
SimplePBKDF2 crypto = new SimplePBKDF2();
PBKDF2Parameters params = crypto.getParameters();
params.setHashCharset("UTF-8");
params.setHashAlgorithm("HmacSHA1");
params.setIterationCount(1000);
if (storedPassword != null) {
new PBKDF2HexFormatter().fromString(params, storedPassword);
}
return crypto.deriveKeyFormatted(plainText);
}
When creating a new password, you would call this function passing null
as the storedPassword
:
String password = hash('MySecretPassword', null);
password
would end up looking something like this:
"192EAEB3B7AA40B1:1000:4C137AF7AD0F3999D18E2B9E6FB726D5C07DE7D5"
When comparing passwords, you call the same function, passing the original password and then compare the results:
String enteredPassword = hash(userInput, password);
if (enteredPassword.equals(password)) {
// Ok!
}
The reason you need to pass the original password is that the hashing parameters and salt are stored in the password hash, so the algorithm needs the stored password to get these parameters and use them for the new hash. However you don't usually need to compare passwords yourself as this is already done in the login module.
Or, roll your own
This blog post gives some explanation on how to roll your own Realm implementation that does add salt. He has source code on GitHub so maybe use that.
It's for Glassfish, but I think it doesn't matter as far as the Realm implementation code goes.