DEV Community

Cover image for How to Securely Store a Password in Java
Andrew (he/him)
Andrew (he/him)

Posted on

How to Securely Store a Password in Java

A previous version of this article confused the process of "hashing" with the process of "encryption". It's worth noting that, while similar, hashing and encryption are two different processes.


Hashing involves a many-to-one transformation, where a given input is mapped to a (usually fixed-size, usually shorter) output, which is not unique to that particular input. In other words, collisions are likely when running a hashing algorithm over a huge range of input values (where different inputs map to the same output). Hashing is an irreversible process because of the nature of hashing algorithms. This SO answer gives a good overview of the differences between hashing and encryption and provides a nice example of why a hashing algorithm might be practically irreversible.

As a mathematical example, consider the modulo (aka. modulus aka. mod) function (expressed here as defined by Donald Knuth):

a % n = mod(a, n) = a - n * floor(a/n)
Enter fullscreen mode Exit fullscreen mode

The simple interpretation of the mod function is that it's the remainder of integer division:

7 % 2 = 1
4 % 3 = 1
33 % 16 = 1
Enter fullscreen mode Exit fullscreen mode

Note how all three examples above give the same output even though they have different inputs. The modulo function is non-invertible because we lose information when we apply it. Even given one of the inputs along with the output, there's no way to calculate the other input value. You just have to guess until you get it right.

Hashing algorithms take advantage of non-invertible functions like this (as well as bit shifts, etc.), and are often repeated many times, astronomically increasing the number of required guesses. The goal of hashing is to make it extremely expensive, computationally, to decode the original information. Oftentimes, it's easier to brute force a hashing algorithm (by trying many possible inputs, as quickly as possible) than to try to "reverse" the hashing algorithm to decode the hashed information.

A common method of deterring even these brute-force attacks is to add a second random piece of information as "salt". This prevents hackers from performing dictionary attacks, where a list of common passwords are mapped to their hashed outputs -- if the hacker knows the hashing algorithm used and can gain access to the database where the hashes are stored, they can use their "dictionary" to map back to the original password and gain access to those accounts. Salting the data means that not only do the hackers have to run a particular password through the hashing algorithm and verify that it matches the hashed output, but they have to re-run that process for every possible value of the salt (usually a string of tens to hundreds of bytes). A random 100-byte salt means (100*2^8 or) 256,000 total password-salt combinations must be tried for each possible password.

Another method of deterring brute-force attacks is to simply require your algorithm to take some long amount of time to run. If your algorithm takes just 2 seconds to run and uses the 100-byte salt mentioned above, it would take nearly 6 days to try all possible salt strings for just a single potential password. This is partly why hashing algorithms are often iterated thousands of times. To produce a secure hash, make sure you re-introduce the salt each time you iterate, otherwise you're setting yourself up for more hash collisions than necessary (see that SO link, above).


Encryption, as opposed to hashing, is always one-to-one and reversible (via decryption). So a particular input will always produce a particular output and only that specific input will produce that specific output. Unlike hashing algorithms, which produce hashes of a fixed length, encryption algorithms will produce variable-length outputs. A good encryption algorithm should produce output which is indistinguishable from random noise, so that patterns in the output cannot be exploited in order to decode it.

Encryption should be used when the data stored needs to be extracted at some point. For instance, messaging apps might encrypt data before transporting it, but that data needs to be decrypted back into plaintext once it's received, so that the recipient can read it. Note that this is not the case with passwords: if your password and the stored salt generate the hashcode stored in the database, then it's very likely that the password you input is the correct one. It's safer to hash the password, then, and just re-calculate the hash code than it is to store an encrypted password which could possibly be decrypted.

But if encrypted information can be decrypted, how can it be secure? Well, there are two standard methods of encryption and decryption, symmetric key encryption and asymmetric key encryption.

Symmetric key encryption means that the same cryptographic key is used to encrypt and decrypt the data. A common analogy used to explain encryption is sending locked packages through the post. If you put a padlock on a box and send it to me, and I have a copy of the key which opens the padlock, then I can easily unlock it and read your message inside. I can send you back a message and you can use the same key to open the box on the other side.

While the box is in transport, no one can open the box and read the message unless they have the same symmetric key that we have. The idea, then, is to keep the key a secret and not share it with anyone other than the intended recipient of the message. Since there are two symmetric private keys, though, if anyone manages to steal (or create) a copy of either private key, all future messages between you and me will be compromised. In other words, we trust each other to keep our keys secure.

Asymmetric key encryption is slightly different, in that you and I both have padlocks and keys, but the first package we send to each other in the post should be our unlocked padlocks:

Then, you can write a message, and lock the message in a box with my padlock. From that point on, the only person who can unlock the box is me, with my private key. When I receive your message, I lock it in a box with your padlock and send it back to you. From that point on, the only person who can unlock that box is you, with your private key.

This method is also known as public key encryption because, oftentimes, the "padlock" in this case is made widely available. Anyone who wants to encrypt a message intended for a particular recipient can do so. Public key encryption is recommended over plaintext passwords because it effectively makes the password much longer and more difficult to guess, reduces the chances of someone "looking over your shoulder" and stealing your password, and is just generally much easier than retyping a password over and over again.

Obviously this barely scratches the surface of hashing and encryption, but I hope it gives you a better understanding of the differences between the two. Now, back to our regularly scheduled programming...


Original article (updated for clarity):


The process of hashing a password in Java can be difficult to get your head around at first, but there are really only three things you need:

  1. a password
  2. a hashing algorithm
  3. some tasty salt

(Note that encryption and decryption is also possible in Java, but is generally not used for passwords, because the password itself doesn't need to be recovered. We just need to check that the password the user enters recreates the hash that we've saved in a database.)


Password-based encryption generates a cryptographic key using a user password as a starting point. We irreversibly convert the password to a fixed-length hash code using a one-way hash function, adding a second random string as "salt", to prevent hackers from performing dictionary attacks, where a list of common passwords are mapped to their hashed outputs -- if the hacker knows the hashing algorithm used and can gain access to the database where the hashcodes are stored, they can use their "dictionary" to map back to the original password and gain access to those accounts.

We can generate salt simply by using Java's SecureRandom class:


import java.security.SecureRandom;
import java.util.Base64;
import java.util.Optional;

  private static final SecureRandom RAND = new SecureRandom();

  public static Optional<String> generateSalt (final int length) {

    if (length < 1) {
      System.err.println("error in generateSalt: length must be > 0");
      return Optional.empty();
    }

    byte[] salt = new byte[length];
    RAND.nextBytes(salt);

    return Optional.of(Base64.getEncoder().encodeToString(salt));
  }
Enter fullscreen mode Exit fullscreen mode

(Note: you could also get a SecureRandom instance with SecureRandom.getInstanceStrong(), though this throws a NoSuchAlgorithmException and so needs to be wrapped in a try{} catch(){} block.)

Next, we need the hashing code itself:

import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;

  private static final int ITERATIONS = 65536;
  private static final int KEY_LENGTH = 512;
  private static final String ALGORITHM = "PBKDF2WithHmacSHA512";

  public static Optional<String> hashPassword (String password, String salt) {

    char[] chars = password.toCharArray();
    byte[] bytes = salt.getBytes();

    PBEKeySpec spec = new PBEKeySpec(chars, bytes, ITERATIONS, KEY_LENGTH);

    Arrays.fill(chars, Character.MIN_VALUE);

    try {
      SecretKeyFactory fac = SecretKeyFactory.getInstance(ALGORITHM);
      byte[] securePassword = fac.generateSecret(spec).getEncoded();
      return Optional.of(Base64.getEncoder().encodeToString(securePassword));

    } catch (NoSuchAlgorithmException | InvalidKeySpecException ex) {
      System.err.println("Exception encountered in hashPassword()");
      return Optional.empty();

    } finally {
      spec.clearPassword();
    }
  }
Enter fullscreen mode Exit fullscreen mode

...there's a lot going on here, so let me explain step-by-step:

  public static Optional<String> hashPassword (String password, String salt) {

    char[] chars = password.toCharArray();
    byte[] bytes = salt.getBytes();
Enter fullscreen mode Exit fullscreen mode

First, we ultimately need the password as a char[], but we have the user pass it in as a String -- (how else would we get a password from the user?) -- so we must convert it to a char[] at the outset. The salt is also passed in as a String and must be converted to a byte[]. The assumption here is that the hashed password and salt will be written to a database as character strings, so we want to generate the salt outside this algorithm as a String and pass it in as a String, as well.

Keeping the user's password in a String is dangerous, because Java Strings are immutable -- once one's made, it can't be overwritten to hide the user's password. So it's best to gather the password, do what we need to do with it, and immediately toss the reference to the original password String so it can be garbage collected. (You can suggest that the JVM garbage collect a dead reference with System.gc(), but garbage collection occurs at unpredictable intervals and cannot be forced to occur.) Similarly, if we convert the password String to a char[], we should clear out the array when we're finished with it (more on that later).

    PBEKeySpec spec = new PBEKeySpec(chars, bytes, ITERATIONS, KEY_LENGTH);
Enter fullscreen mode Exit fullscreen mode

Here, we're specifying how we're going to generate the hashed password. chars is the plaintext password as a char[], bytes is the salt String converted to a byte[], ITERATIONS is how many times we should perform the hashing algorithm, and KEY_LENGTH is the desired length of the resulting cryptographic key, in bits.

When we perform the hashing algorithm, we take the plaintext password and the salt and generate some pseudorandom output string. Iterated hashing algorithms will then repeat that process some number of times, using the output from the first hash as the input to the second hash. This is also known as key stretching and it greatly increases the time required to perform brute-force attacks (having a large salt string also makes these kind of attacks more difficult). Note that while increasing the number of iterations increases the time required to hash the password and makes your database less vulnerable to brute-force attacks, it can also make you more vulnerable to DoS attacks because of the extra processing time required.

The length of the final hashed password is limited by the algorithm used. For example, "PBKDF2WithHmacSHA1" allows hashes of up to 160 bits, while "PBKDF2WithHmacSHA512" hashes can be as long as 512 bits. Specifying a KEY_LENGTH longer than the maximum key length of the algorithm you've chosen will not lengthen the key beyond its specified maximum, and can actually slow down the algorithm.

Finally, note that spec holds all of the information about the algorithm, the original plaintext password, and so on. We really want to delete all of that when we're finished.

    Arrays.fill(chars, Character.MIN_VALUE);
Enter fullscreen mode Exit fullscreen mode

We're done with the chars array now, so we can clear it out. Here, we set all elements of the array to \000 (the null character).

    try {
      SecretKeyFactory fac = SecretKeyFactory.getInstance(ALGORITHM);
      byte[] securePassword = fac.generateSecret(spec).getEncoded();
      return Optional.of(Base64.getEncoder().encodeToString(securePassword));

    } catch (NoSuchAlgorithmException | InvalidKeySpecException ex) {
      System.err.println("Exception encountered in hashPassword()");
      return Optional.empty();

    } finally {
      spec.clearPassword();
    }
Enter fullscreen mode Exit fullscreen mode

Our hashPassword() method ends on this try{} catch(){} block. In it, we first get the algorithm that we defined earlier ("PBKDF2WithHmacSHA512") and we use that algorithm to hash the plaintext password, according to the specifications laid out in spec. generateSecret() returns a SecretKey object, which is "an opaque representation of the cryptographic key", meaning that it contains only the hashed password and no other identifying information. We use getEncoded() to get the hashed password as a byte[] and save it as securePassword.

If this all goes off without a hitch, we encode that byte[] in base-64 (so it's composed only of printable ASCII characters) and return it as a String. We do this so that the hashed password can be saved in a database as a character string without any encoding issues.

If there are any Exceptions during the encryption process, we return an empty Optional. Otherwise, we finish the method by clearing the password from spec. Now, there are no references left to the original, plaintext password in this method. (Note that the finally block is executed whether or not there is an Exception and before anything is returned from any preceding try or catch blocks, so it's safe to have it at the end like this -- the password will be cleared from spec.)

The last thing to do is write a small method which determines whether or not a given plaintext password generates the hashed password, when the same salt is used. In other words, we want a function that tells us if the entered password is correct or not:

  public static boolean verifyPassword (String password, String key, String salt) {
    Optional<String> optEncrypted = hashPassword(password, salt);
    if (!optEncrypted.isPresent()) return false;
    return optEncrypted.get().equals(key);
  }
Enter fullscreen mode Exit fullscreen mode

The above uses the original salt string and a plaintext password in question to generate a hashed password which is compared against the previously-generated hash key. This method returns true only if the password is correct and there were no errors re-hashing the plaintext password.

Okay, now that we have all of this, let's test it! (Note: I have these methods defined in a utility class called PasswordUtils, in a package called watson.)

jshell> import watson.*

jshell> String salt = PasswordUtils.generateSalt(512).get()
salt ==> "DARMFcJcJDeNMmNMLkZN4rSnHV2OQPDd27yi5fYQ77r2vKTa ... Wt9QZog0wtkx8DQYEAOOwQVs="

jshell> String password = "Of Salesmen!"
password ==> "Of Salesmen!"

jshell> String key = PasswordUtils.hashPassword(password, salt).get()
key ==> "djaaKTM/+X14XZ6rxjN68l3Zx4+5WGkJo3nAs7KzjISiT6aa ... sN5DcmOeMfhqMGCNxq6TIhg=="

jshell> PasswordUtils.verifyPassword("Of Salesmen!", key, salt)
$5 ==> true

jshell> PasswordUtils.verifyPassword("By-Tor! And the Snow Dog!", key, salt)
$6 ==> false
Enter fullscreen mode Exit fullscreen mode

Works like a charm! Go forth and hash!

Top comments (21)

Collapse
 
madhadron profile image
Fred Ross

A couple comments:

  1. Aside from making sure you're not retaining references to it forever, worrying about trying to overwrite the the String containing the user's password is basically futile. There are likely lots of copies made of that string along the way. If someone has access to your program's memory, it's not protected anyway.

  2. At this point in time, please use argon2 or scrypt as your password hashing algorithm. They force much larger use of memory which makes brute force attack schemes less feasible on GPUs and more expensive on ASICs and FPGUs.

  3. Using a more expensive password hashing scheme should never be a vector for a DoS attack. The correct solution is to implement exponential backoff on repeated failed login attempts: the first failure lets you try again in 100ms, the second failure in 200ms, the third in 400ms, etc. The exception is in environments that specify otherwise, such as health care in the USA, where HIPAA specifies three tries then lockout.

Collapse
 
awwsmm profile image
Andrew (he/him)

Fair points. Thanks for taking the time to read and comment. These are definitely things I'll have to change if I implement this commercially. (This example was for a term project for a class I took.)

Collapse
 
terracraft profile image
Terracraft

I heard that there's ASIC rigs for mining scrypt

Collapse
 
edsaavedra84 profile image
Eduardo Saavedra

Cool material, very useful!
BTW, thanks for the *Rush * references :)

Collapse
 
pojntfx profile image
Felicitas Pojtinger
// Java
import java.security.SecureRandom;

private static final SecureRandom RAND = new SecureRandom();

System.out.println(RAND)
Enter fullscreen mode Exit fullscreen mode

Just in case someone ever asks me why I refuse to use Java ;)

// JavaScript/TypeScript
import { hash } from 'bcrypt'

const random: string = hash('seed', 10)

console.log(random)
Enter fullscreen mode Exit fullscreen mode

Very nice article though!

Collapse
 
billoneil profile image
Bill O'Neil

You can use BCrypt in Java as well Hashing passwords in Java with BCrypt.

public String hash(String password) {
    return BCrypt.hashpw(password, BCrypt.gensalt(logRounds));
}

public boolean verifyHash(String password, String hash) {
    return BCrypt.checkpw(password, hash);
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
caseywebb profile image
Casey Webb

While this is a well written guide, it should be pointed out that hashing != encryption. Hashing is 1-way, encryption is 2-way. In other words, you can't decrypt a hash, you can only check that rehashing the same value gives the same results.

Collapse
 
awwsmm profile image
Andrew (he/him)

Thanks for the heads-up! I'm working on an amended version of the article that discusses this issue. I'll post it tonight or tomorrow.

Collapse
 
awwsmm profile image
Andrew (he/him)

Updated. Let me know what you think!

Collapse
 
caseywebb profile image
Casey Webb

Looking good!

Collapse
 
theodesp profile image
Theofanis Despoudis

I've seen the same implementation in PHP done in 3 lines

Collapse
 
shalvah profile image
Shalvah

Yes, most of us agree that dynamically typed languages are easier/faster to code in and generally involve less lines of code But your comment isn't very encouraging, and doesn't add value to this post. Maybe I'm misreading, but it sounds fairly hostile. Do reconsider next time.

Collapse
 
awwsmm profile image
Andrew (he/him)

If anyone's interested, here are different implementations of this general procedure in languages like PHP, Ruby, JavaScript, and so on. The PHP implementation is indeed just 3 lines:

password_hash($password, PASSWORD_BCRYPT);
$salt = '$2y$10$' . mcrypt_create_iv(22);
$salted_password = crypt($password, $salt);
Enter fullscreen mode Exit fullscreen mode
Collapse
 
theodesp profile image
Theofanis Despoudis • Edited

Yes, I remember because I had to port a similar algorithm in Java and it was like 200 lines of code vs this one!

Collapse
 
awwsmm profile image
Andrew (he/him)

Could you post it?

Collapse
 
vdelitz profile image
vdelitz

Loved reading through some of your articles and have to admit, you have a great style when explaining things. As you have demonstrated to have great coding skills: have you ever worked on WebAuthn / passkeys? How was the developer experience in your opinion?

Collapse
 
rafasmxp profile image
rafuru

It's a great idea to toss the password string to the garbage collector ASAP, I've saw a lot of implementation which doesn't consider this fact.

Collapse
 
awwsmm profile image
Andrew (he/him)

Yeah, unfortunately, there's no way to force garbage collection in Java. You can only suggest it by calling System.gc().

Collapse
 
ondrejs profile image
Ondrej

Correct. Good article btw, I would probably use the Kotlin/Java implementation of NaCl library but in this case the ones you used are strong enough.

Collapse
 
aavulamanudeep profile image
AavulaManudeep • Edited

I am try to check the Hashpasword with .equals() method but it is showing stored password and user entered password as false even though both are same please suggest me a code fix. HashedPassword is saving is working fine while registering.

//Java
public boolean userAuthentication(Userdetails userdetails)
{
Optional salt = passwordUtils.generateSalt(CableTVConstants.SALT_LENGTH);
Optional userinfo = userDetailService.findById(userdetails.getUsername());
if(userinfo.isPresent())
{
return passwordUtils.verifypassword(userdetails.getPassword(),userinfo.get().getPassword(),salt.get());
}
logger.log(Level.ALL,"Invalid user credentials");
return false;
}
public boolean verifypassword(String password, String key, String salt)
{
Optional password_check = generateHashPassword(password,salt);
if(!password_check.isPresent())
{
return false;
}
return password_check.get().equals(key);
}

Collapse
 
kirankamath96 profile image
Kiran Kamath

I have a requirement of taking a password from the user and then when he needs to see, then I must decrypt it and show on screen, is this method can be used for decryption.