Hashing passwords in Java
How to secure passwords with modern secure algorithms
Encrypt or hash?
Encrypting is a two way function and it’s reversible: you can decrypt the encrypted string to get original string if you have the key. That means your application has to store such key somewhere and an attacker who gets access to your database can get the original passwords by getting both the key and the encrypted text.
Whereas with a hash it’s impossible.
Hashing is a one way function and it’s irreversible: you apply the secure hash algorithm and you cannot get the original string back. The most you can do is to generate what’s called a “collision”, that is, finding a different string that provides the same hash. You can find more information about this here since this article won’t cover theoretical aspects.
What is important is that exists a subset of hash functions called Cryptographic Hash Functions (CHF) that are designed to protect passwords by producing secure hashes. Along with them there is a family of functions called Key Derivation Functions (KDF) that take the input password (which may contain biases) and stretch it in order to have a randomly distributed input for the CHFs.
In this article we will explore the KDFs, that are de-facto the most secure way to protect passwords.
What you need
Since Java does not provide those algorithms natively, you have to install a third party library like Password4j which enables on your system bcrypt, scrypt and Argon2, along with others.
The advantages of this library over the other solutions are:
- Fluent and intuitive APIs
- Not relying on JNI (the implementation is 100% Java)
- Algorithms can be upgraded easily
- Very few dependencies
- Portable configurations
The source code of the project can be found here.
Installation
On your pom.xml
add the following dependency:
And that’s all! Now you are ready to use the most secure cryptographic functions to protect passwords in your project. If you need more details about installation, get a look at the official wiki.
Things to know first
Password4j uses some universal concepts which are worth mentioning
- salt: random data used as additional input with the password. For each password a new salt must be generated and stored alongside in the database. Modern algorithms already embed the salt in the final hash.
- pepper (optional): a secret added to each password. This data must be stored in a different database (or in a configuration file) and typically is shared among all the users.
- configuration: each algorithm has its own parameters and they are embedded within the hash. Password4j can retrieve these parameters and build a prototype function to verify the hash against the password provided by the user. Additionally you can explicit the configuration programmatically or in a properties file.
Bcrypt
Bcrypt uses 2 parameters: rounds and minor version.
More rounds you use and more secure against brute-force attacks; this number should also depends on the desired responsiveness of your system. The minor version should always be the latest one (2b
), unless you need backward-compatibility.
Hash
In this example we will hash the password using 12 rounds and version 2b
. Note that bcrypt randomly generates by itself the salt: this means the hash will be always different for the same input.
The hash produced can be safely stored in the database. You don’t have to store the salt, that is automatically generated, because it is already encoded within the hash, along with the number of rounds and the version.
In all the examples you will find the usage of pepper, that is a completely optional parameter. Take in account that if it is used during the hashing process it is required for the verification.
Verify
You just need to retrieve the hash from the database and do the following:
Alternatively, you may not specify the configuration used and let Password4j retrieve the data from the hash with Password.getInstanceFromHash
:
This is very useful if your database contains mixed hashes coming from different configurations.
As you can see, just the pepper is necessary because is not encoded in the hash. You can find more information on bcrypt usage in the official wiki of the project.
Scrypt
Scrypt uses 3 (optionally 4) parameters: work factor, resources and parallelisation.
OWASP recommends using it with a minimum work factor of 2¹⁶, a minimum resource of 8 (1024 bytes block size) and a parallelisation level of 1.
Hash
In this example we will use the aforementioned configuration suggested by OWASP:
Verify
You just need to retrieve the hash from the database and do the following:
Alternatively, you may not specify the configuration used and let Password4j retrieve the data from the hash:
You can find more information on scrypt usage in the official wiki of the project.
Argon2
Argon2 uses 5 (optionally 6) parameters: memory, number of iterations, parallelisation, length and type.
OWASP recommends using it with a minimum memory of 15MiB, 2 iterations, 1 degree of parallelisation, 32 bytes of length and type Argon2id
.
Hash
In this example we will use the aforementioned configuration suggested by OWASP:
Verify
You just need to retrieve the hash from the database and do the following:
Alternatively, you may not specify the configuration used and let Password4j retrieve the data from the hash:
You can find more information on Argon2 usage in the official wiki of the project.
Migrating from an algorithm to another
If you need to change the algorithm used by your system, Password4j can “upgrade” the hash during the verification process.
In this example we migrate from bcrypt to Argon2 and it is done when the user prompt their password: in one step the old hash is verified and a new one is produced.
Choose the parameters
It is not always clear how to determine the parameters to use for a specific algorithm. A high level heuristic is to choose a maximum time for the hash to be produced (that is the acceptable time for a user to login — short times are more appreciated for web applications) and set the parameters so that the threshold time is observed.
For example, our application uses bcrypt and we want that a user can login within 1500ms.
Password4j can help us by determining the number of rounds necessary to bcrypt in order to produce an hash within 1500ms:
It actually takes 929ms with 14 rounds. Be aware that changing the number of rounds changes the execution time exponentially. Also take in account that this routine should be executed in the target system because performance varies for different environments.
This feature is available for scrypt and Argon2 as well. Check this page for more details.
Conclusion
So, we’ve taken a deep dive into password hashing, understanding why encryption is not the best way to protect password.
We demonstrate how to hash passwords in Java with the help of Password4j and secure passwords with the most modern algorithms: bcrypt, scrypt and Argon2.
Finally we saw how to migrate from old/less secure algorithms to a better one with almost zero effort and how to understand what are the optimal parameters for our environments.