PBKDF2 with HMAC-SHA1 encryption class

Standard

Sensitive user data must insure confidentiality and integrity. The following class is a example of how to use a password based key derivation function (PBKDF2) algorithm to encode / decode data. Using the createHash method a salt and hash byte array is generated from a instance of the PBKDF2WithHmacSHA1 from the secretKeyFactory. The byte arrays that are returned serve as your encrypted data. You must have both to be able to validate.

Use case

A user signs up for a account on your website. They enter a username / password. The password that is entered is ran against the createHash method. The result (salt, hash) are stored in a database. This is the users login data encrypted. Whenever a user attempts to login with that username, the salt and hash are retrieved from the database and the validatePassword method is called. If the test passes you are assured they are a legitimate user and may proceed.

What is slowEquals?

When a password is validated it requires that slowEquals be true. SlowEquals is a method designed to prevent against timing attacks. What this does is assure that the attacker cannot determine how long it took for the password to fail. It iterates through all values in the byte array regardless if they are equal or not. This prevents the attacker from having enough information compute a off line attack.

public class HmacSHA1 {

    // Used in encryption for how many cycles
    private static final int PBKDF2_ITERATIONS = 2000;
    private static final Integer checkLoops = 10;
    private static final String password = "changeit";

    /**
     * Typical test case to assure that Encoding / Decoding / Encryption work
     * properly
     *
     * @param args
     */
    public static void main(String[] args) {

        System.out.println("Encryption Test\n");
        
        for (int i = 0; i <= checkLoops; i++) {
            
             try {
                // Create has in the form of (salt:hash)
                String hash = createHash(password);
                String[] params = hash.split(":");
                byte[] salt = fromHex(params[0]);
                byte[] hash5 = fromHex(params[1]);

                // Compare our generated bytes to the orignal
                if (validatePassword(password, hash5, salt)) {
                    System.out.println("valid password!");
                }
            } catch (NoSuchAlgorithmException | InvalidKeySpecException ex) {
                System.out.println("ERROR: " + ex);
            }
        }
    }

    /**
     * Validates a password using a hash.
     *
     * @param password the password to check
     * @param hash
     * @param salt
     * @return true if the password is correct, false if not
     * @throws java.security.NoSuchAlgorithmException
     * @throws java.security.spec.InvalidKeySpecException
     */
    public static boolean validatePassword(char[] password, byte[] hash, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException {

        // Compute the hash of the provided password, using the same salt, iteration count, and hash length
        byte[] testHash = pbkdf2(password, salt, PBKDF2_ITERATIONS, hash.length);

        // Compare the hashes in constant time. The password is correct if both hashes match.
        return slowEquals(hash, testHash);
    }

    /**
     * Validates a password using a hash.
     *
     * @param password the password to check
     * @param hash
     * @param salt
     * @return true if the password is correct, false if not
     * @throws java.security.NoSuchAlgorithmException
     * @throws java.security.spec.InvalidKeySpecException
     */
    public static boolean validatePassword(String password, byte[] hash, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException {

        return validatePassword(password.toCharArray(), hash, salt);
    }

    /**
     * Computes the PBKDF2 hash of a password.
     *
     * @param password the password to hash.
     * @param salt the salt
     * @param iterations the iteration count (slowness factor)
     * @param bytes the length of the hash to compute in bytes
     * @return the PBDKF2 hash of the password
     */
    private static byte[] pbkdf2(final char[] password, final byte[] salt, final int iterationCount, final int keyLength) {

        try {
            return SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1").generateSecret(new PBEKeySpec(password, salt, iterationCount, keyLength * 8)).getEncoded();
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Returns a salted PBKDF2 hash of the password.
     *
     * @param password the password to hash
     * @return a salted PBKDF2 hash of the password
     * @throws java.security.NoSuchAlgorithmException
     * @throws java.security.spec.InvalidKeySpecException
     */
    public static String createHash(String password) throws NoSuchAlgorithmException, InvalidKeySpecException {
        return createHash(password.toCharArray());
    }

    /**
     * Returns a salted PBKDF2 hash of the password.
     *
     * @param password the password to hash
     * @return a salted PBKDF2 hash of the password in the form of SALT:HASH
     * @throws java.security.NoSuchAlgorithmException
     * @throws java.security.spec.InvalidKeySpecException
     */
    public static String createHash(char[] password) throws NoSuchAlgorithmException, InvalidKeySpecException {

        // Generate a random salt
        byte[] salt = getSalt().getBytes();

        // Hash the password
        byte[] hash = pbkdf2(password, salt, PBKDF2_ITERATIONS, 24);

        // Format salt:hash
        return toHex(salt) + ":" + toHex(hash);
    }

    /**
     *
     * @return
     * @throws NoSuchAlgorithmException
     */
    private static String getSalt() throws NoSuchAlgorithmException {
        SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
        byte[] salt = new byte[16];
        sr.nextBytes(salt);
        return Arrays.toString(salt);
    }

    /**
     * Converts a byte array into a hexadecimal string.
     *
     * @param array the byte array to convert
     * @return a length*2 character string encoding the byte array
     */
    private static String toHex(byte[] array) {

        BigInteger bi = new BigInteger(1, array);
        String hex = bi.toString(16);
        int paddingLength = (array.length * 2) - hex.length();
        if (paddingLength > 0) {
            return String.format("%0" + paddingLength + "d", 0) + hex;
        } else {
            return hex;
        }
    }

    /**
     * Converts a string of hexadecimal characters into a byte array.
     *
     * @param hex the hex string
     * @return the hex string decoded into a byte array
     */
    public static byte[] fromHex(String hex) {

        byte[] binary = new byte[hex.length() / 2];
        for (int i = 0; i < binary.length; i++) {
            binary[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16);
        }
        return binary;
    }

    /**
     * Compares two byte arrays in length-constant time. This comparison method
     * is used so that password hashes cannot be extracted from an on-line
     * system using a timing attack and then attacked off-line.
     *
     * @param a the first byte array
     * @param b the second byte array
     * @return true if both byte arrays are the same, false if not
     */
    private static boolean slowEquals(byte[] a, byte[] b) {

        int diff = a.length ^ b.length;
        for (int i = 0; i < a.length && i < b.length; i++) {
            diff |= a[i] ^ b[i];
        }
        return diff == 0;
    }

}

The complete project is available on Github.