Secure password hashing

A secure hash is a one-way cryptographic hash of a message whose producing algorithm should be intentionally slow. Why is the latter part important? It combats against brute-force and word list attacks. That is, it requires so much time to create a hash from an attempt, searching the entire password space becomes infeasible. To increase complexity further, the hash can be salted. Refer to Message digest for more details on general hashing algorithms and salting.

The following example follows NIST Special Publication 800-132, which recommends Password-Based Key Derivation (PBKDF) – an intentionally slow method for producing secure hashes. Java has supported PBKDF2WithHmacSHA1 since 1.6, thus the choice for compliance in this example.

import static java.lang.System.err;
import static java.lang.System.out;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.Random;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
 
/**
 * Secure hash class.
 * @author Ray Hylock
 */
public class SecureHash {
    // temporary users for this example - use database in production
    private HashMap<String, User> users = new HashMap<String, User>();
    private static final int SALT_BYTES = 32;
    private static final int ITERATIONS = 1000;
    private static final int KEY_LENGTH = 512;
    private static final String INSTANCE = "PBKDF2WithHmacSHA1";
     
    /** Constructor. */
    public SecureHash(){}
     
    /**
     * Adds a user to the system.
     * @param username      username
     * @param password      password
     */
    public void addUser(String username, String password){
        if(users.containsKey(username)) err.println("The user already exists.");
        else {
            byte salt[] = randomSalt();
            byte hash[] = secureHash(password, salt);
            users.put(username, new User(username, hash, salt));
        }
    }
     
    /**
     * Checks if the passed credentials are valid.
     * @param username  username
     * @param password  password
     * @return          {@code true} if valid, {@code false} otherwise
     */
    public boolean isValid(String username, String password){
        User u = users.get(username);
        if(u != null) return u.validate(secureHash(password, u.getSalt()));
        else return false;
    }
     
    /**
     * Returns the secure hash given the password and salt.
     * @param password  password
     * @param salt      user-specific salt
     * @return          secure hash as {@code byte[]}
     */
    private byte[] secureHash(String password, byte[] salt){
        try {
            SecretKeyFactory skf = SecretKeyFactory.getInstance(INSTANCE);
            PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, ITERATIONS, KEY_LENGTH);
            SecretKey key = skf.generateSecret(spec);
            return key.getEncoded();
             
        } catch (NoSuchAlgorithmException ex) {
            err.println("Failed to get algorithm instance.\n" + ex.getMessage());
        } catch (InvalidKeySpecException ex) {
            err.println("Invalid key specification.\n" + ex.getMessage());
        }
        return null;
    }
     
    /**
     * Creates a random salt.
     * @return random salt as a {@code byte[]}
     */
    private byte[] randomSalt(){
        final Random r = new SecureRandom();
        byte salt[] = new byte[SALT_BYTES];
        r.nextBytes(salt);
        return salt;
    }
     
    /**
     * For testing only, this will print user specific information.
     * @param username username for which the data will be printed
     */
    public void printUser(String username){
        if(users.get(username) != null) users.get(username).print();
        else err.println("User " + username + " does not exist");
    }
     
    /**
     * This class stores user information necessary for creating and testing
     * secure hashes. For testing, this is just a simple populated class. In 
     * practice, this will more than likely be a database table.
     */
    class User {
        private String username;
        private byte[] hash;
        private byte[] salt;
 
        /**
         * Instantiate a new user.
         * @param username  username
         * @param hash      secure hash
         * @param salt      personalized salt
         */
        public User(String username, byte[] hash, byte[] salt){
            this.username = username;
            this.hash = new byte[hash.length];
            System.arraycopy(hash, 0, this.hash, 0, hash.length);
            this.salt = new byte[salt.length];
            System.arraycopy(salt, 0, this.salt, 0, salt.length);
        }
         
        /**
         * Validates a passed secure hash.
         * @param hash  hash to validate
         * @return      {@code true} if valid, {@code false} otherwise
         */
        public boolean validate(byte[] hash){
            return Arrays.equals(this.hash, hash);
        }
         
        /**
         * Get the user-specific salt.
         * @return the salt for the user
         */
        public byte[] getSalt(){
            return salt;
        }
         
        /**
         * For testing only, this will print the username, salt, and hash.
         */
        public void print(){
            out.println("Username: " + username);
            out.println("Salt: " + Base64.getEncoder().encodeToString(salt));
            out.println("Hash: " + Base64.getEncoder().encodeToString(hash));
        }
    }
}

The following is an example implementation of the SecureHash class that (1) sets a user, (2) trys to login with incorrect credentials, and (3) trys to log with correct credentials.

import static java.lang.System.out;
public class SecureHashTest {
    private static SecureHash sh;
    public static void main(String[] args) {
        // setup
        sh = new SecureHash();
        String username = "username", password = "password";
         
        // add user and print stored information
        out.println("Adding user: "+username+"/"+password);
        sh.addUser(username, password);
        sh.printUser(username);
         
        // compare with incorrect username
        out.println("\nValidating: "+username+"1/"+password);
        validate(username+"1", password);
         
        // compare with correct username
        out.println("\nValidating: "+username+"/"+password);
        validate(username, password);
    }
     
    /**
     * Validate the user.
     * @param username      username
     * @param password      password
     */
    private static void validate(String username, String password){
        boolean valid = sh.isValid(username, password);
        if(valid) out.println("Successfully validated");
        else out.println("Invalid credentials.");
    }
}

Output from the example class:

Adding user: username/password
Username: username
Salt: suyp6NXDbxz3kvV8GaeUs9m6k9d05Is1Dssv5pOYT1k=
Hash: Br7Wq1bbG1S3lUG6phPQTdfJEDYjKujI44HbQsMp4GP0k71gWBiUe71mzb0XkVkxef5LWWOu358nKR3vFKmpZA==
 
Validating: username1/password
Invalid credentials.
 
Validating: username/password
Successfully validated