Learn Java hashing algorithms in-depth for hashing passwords. A secure password hash is an encrypted sequence of characters obtained after applying specific algorithms and manipulations on user-provided passwords, which are generally very weak and easy to guess.
Many such hashing algorithms in Java can prove effective for password security.
Important
Please remember that once the password hash has been generated, we can not convert the hash back to the original password.
Each time a user login into the application, we must generate the password hash again and match it with the hash stored in the database.
So, if a user forgets his/her password, we will have to send him a temporary password; or ask him to reset the password. It’s common nowadays, right?
1. Simplest Password Hash with MD5 Algorithm
The MD5 Message-Digest Algorithm is a widely used cryptographic hash function that produces a 128-bit (16-byte) hash value. It’s very simple and straightforward; the basic idea is to map data sets of variable length to data sets of a fixed size.
To do this, the input message is split into chunks of 512-bit blocks. Padding is added to the end so that its length can be divided by 512.
These blocks are processed by the MD5 algorithm, which operates in a 128-bit state, and the result will be a 128-bit hash value. After applying MD5, the generated hash is typically a 32-digit hexadecimal number.
Here, the password to be encoded is often called the “message” and the generated hash value is called the message digest or simply “digest”.
1.1. Java MD5 Hashing Example
importjava.security.MessageDigest;importjava.security.NoSuchAlgorithmException;publicclassSimpleMD5Example{publicstaticvoidmain(String[] args){String passwordToHash ="password";String generatedPassword =null;try{// Create MessageDigest instance for MD5MessageDigest md =MessageDigest.getInstance("MD5");// Add password bytes to digest
md.update(passwordToHash.getBytes());// Get the hash's bytesbyte[] bytes = md.digest();// This bytes[] has bytes in decimal format. Convert it to hexadecimal formatStringBuilder sb =newStringBuilder();for(int i =0; i < bytes.length; i++){
sb.append(Integer.toString((bytes[i]&0xff)+0x100,16).substring(1));}// Get complete hashed password in hex format
generatedPassword = sb.toString();}catch(NoSuchAlgorithmException e){
e.printStackTrace();}System.out.println(generatedPassword);}}
Output
5f4dcc3b5aa765d61d8327deb882cf99
1.2. Disadvantages
Although MD5 is a widely spread hashing algorithm, is far from being secure, MD5 generates fairly weak hashes. Its main advantages are that it is fast, and easy to implement. But it also means that it is susceptible tobrute-force and dictionary attacks.
Rainbow tables with words and hashes allow searching very quickly for a known hash and guessing the original password.
MD5 is not collision resistant which means that different passwords can eventually result in the same hash.
If you are using MD5 hash in your application, consider adding some salt to your security.
2. Making MD5 More Secure using Salt
Keep in mind, adding salt is not specific to MD5. We can add a Salt to every other algorithm also. So, please focus on how it is applied rather than its relation with MD5.
Wikipedia defines salt as random data that are used as an additional input to a one-way function that hashes a password or pass-phrase.
In more simple words, salt is some randomly generated text, which is appended to the password before obtaining hash.
The original intent of salting was primarily to defeat pre-computed rainbow table attacks that could otherwise be used to significantly improve the efficiency of cracking the hashed password database.
A more significant benefit is to slow down parallel operations that compare the hash of a password guess against many password hashes at once.
Important
We always need to use a SecureRandom to create good salts. The Java SecureRandom class supports the “SHA1PRNG” pseudo-random number generator algorithm, and we can take advantage of it.
2.1. How to generate Salt
Let’s see how we should generate salt.
privatestaticStringgetSalt()throwsNoSuchAlgorithmException,NoSuchProviderException{// Always use a SecureRandom generatorSecureRandom sr =SecureRandom.getInstance("SHA1PRNG","SUN");// Create array for saltbyte[] salt =newbyte[16];// Get a random salt
sr.nextBytes(salt);// return saltreturn salt.toString();}
SHA1PRNG algorithm is used as a cryptographically strong pseudo-random number generator based on the SHA-1 message-digest algorithm.
Note that if a seed is not provided, it will generate a seed from a true random number generator (TRNG).
2.2. Generate MD5 with Salt
Now, let’s look at the modified MD5 hashing example:
importjava.security.MessageDigest;importjava.security.NoSuchAlgorithmException;importjava.security.NoSuchProviderException;importjava.security.SecureRandom;publicclassSaltedMD5Example{publicstaticvoidmain(String[] args)throwsNoSuchAlgorithmException,NoSuchProviderException{String passwordToHash ="password";String salt =getSalt();String securePassword =getSecurePassword(passwordToHash, salt);System.out.println(securePassword);String regeneratedPassowrdToVerify =getSecurePassword(passwordToHash, salt);System.out.println(regeneratedPassowrdToVerify);}privatestaticStringgetSecurePassword(String passwordToHash,String salt){String generatedPassword =null;try{// Create MessageDigest instance for MD5MessageDigest md =MessageDigest.getInstance("MD5");// Add password bytes to digest
md.update(salt.getBytes());// Get the hash's bytesbyte[] bytes = md.digest(passwordToHash.getBytes());// This bytes[] has bytes in decimal format;// Convert it to hexadecimal formatStringBuilder sb =newStringBuilder();for(int i =0; i < bytes.length; i++){
sb.append(Integer.toString((bytes[i]&0xff)+0x100,16).substring(1));}// Get complete hashed password in hex format
generatedPassword = sb.toString();}catch(NoSuchAlgorithmException e){
e.printStackTrace();}return generatedPassword;}// Add saltprivatestaticStringgetSalt()throwsNoSuchAlgorithmException,NoSuchProviderException{// Always use a SecureRandom generatorSecureRandom sr =SecureRandom.getInstance("SHA1PRNG","SUN");// Create array for saltbyte[] salt =newbyte[16];// Get a random salt
sr.nextBytes(salt);// return saltreturn salt.toString();}}
Please note that you must now store this salt value for every password you hash. Because when user login back into the system, we must use only the originally generated salt to create the hash again to match with the stored hash. If a different salt is used (we are generating random salt), the then generated hash will be different.
Also, you might hear of the terms crazy hashing and salting. It generally refers to creating custom combinations.
alt + password + salt => Generated hash
Do not practice these crazy things. They do not help in making hashes further secure anyhow. If you want more security, choose a better algorithm.
3. Better Password Security using SHA Algorithms
The SHA (Secure Hash Algorithm) is a family of cryptographic hash functions. It is very similar to MD5, except it generates more strong hashes.
However, SHA hashes are not always unique, and it means that we could have equal hashes for two different inputs. When this happens, it’s called a “collision”. The chances of collision in SHA are less than MD5. But, do not worry about these collisions because they are very rare.
Java has four implementations of the SHA algorithm. They generate the following length hashes in comparison to MD5 (128-bit hash):
SHA-1 (Simplest one – 160 bits Hash)
SHA-256 (Stronger than SHA-1 – 256 bits Hash)
SHA-384 (Stronger than SHA-256 – 384 bits Hash)
SHA-512 (Stronger than SHA-384 – 512 bits Hash)
A longer hash is more challenging to break. That’s the core idea.
To get any implementation of the algorithm, pass it as a parameter to MessageDigest. e.g.
Very quickly, we can say that SHA-512 generates the most robust Hash.
4. Stronger Hashes using PBKDF2WithHmacSHA1 Algorithm
So far, we have learned about creating secure hashes for passwords and using salt to make it even more secure. But the problem today is that hardwares have become so fast than any brute force attack using dictionary and rainbow tables, a bad actor can crack any password in less or more time.
To solve this problem, the general idea is to make brute force attacks slower to minimize damage. Our following algorithm works on this very concept.
The goal is to make the hash function slow enough to impede attacks but still fast enough not to cause a noticeable delay for the user.
This feature is essentially implemented using some CPU-intensive algorithms such as PBKDF2, Bcrypt or Scrypt. These algorithms take a work factor (also known as security factor) or iteration count as an argument.
Iteration count determines how slow the hash function will be. When computers become faster next year, we can increase the work factor to balance it out.
The next step is to have a function that we can use to validate the password again when the user comes back and login.
publicstaticvoidmain(String[] args)throwsNoSuchAlgorithmException,InvalidKeySpecException{String originalPassword ="password";String generatedSecuredPasswordHash
=generateStorngPasswordHash(originalPassword);System.out.println(generatedSecuredPasswordHash);boolean matched =validatePassword("password", generatedSecuredPasswordHash);System.out.println(matched);
matched =validatePassword("password1", generatedSecuredPasswordHash);System.out.println(matched);}privatestaticbooleanvalidatePassword(String originalPassword,String storedPassword)throwsNoSuchAlgorithmException,InvalidKeySpecException{String[] parts = storedPassword.split(":");int iterations =Integer.parseInt(parts[0]);byte[] salt =fromHex(parts[1]);byte[] hash =fromHex(parts[2]);PBEKeySpec spec =newPBEKeySpec(originalPassword.toCharArray(),
salt, iterations, hash.length *8);SecretKeyFactory skf =SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");byte[] testHash = skf.generateSecret(spec).getEncoded();int diff = hash.length ^ testHash.length;for(int i =0; i < hash.length && i < testHash.length; i++){
diff |= hash[i]^ testHash[i];}return diff ==0;}privatestaticbyte[]fromHex(String hex)throwsNoSuchAlgorithmException{byte[] bytes =newbyte[hex.length()/2];for(int i =0; i < bytes.length ;i++){
bytes[i]=(byte)Integer.parseInt(hex.substring(2* i,2* i +2),16);}return bytes;}
Please refer to functions from the above code samples. If found any difficulty, then download the source code attached at the end of the tutorial.
5. Hashes using Bcrypt and Scrypt
The concepts behind bcrypt is similar to the previous concept as in PBKDF2. It just happened to be that Java does not have any inbuilt support for bcrypt algorithm to make the attack slower but still, you can find one such implementation in the attached source code.
5.1. Creating Hash using Bcrypt with Salt
Let’s look at the sample usage code (BCrypt.java is available in the source code).
Storing the text password with hashing is the most dangerous thing for application security today.
MD5 provides basic hashing for generating secure password hash. Adding salt makes it further stronger.
MD5 generates a 128-bit hash. Use the SHA algorithm to make it more secure, which produces hashes from 160-bit to 512-bit long. 512-bit is the strongest.
Even SHA-hashed secure passwords can be cracked with today’s fast hardwares. To beat that, you will need algorithms to make the brute force attacks slower and minimize the impact. Such algorithms are PBKDF2, BCrypt and SCrypt.
Please take a well-considered thought before applying the appropriate security algorithm.
To download the source code of the above algorithm examples, please follow the below link.
A fun-loving family man, passionate about computers and problem-solving, with over 15 years of experience in Java and related technologies.
An avid Sci-Fi movie enthusiast and a fan of Christopher Nolan and Quentin Tarantino.
Comments