All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.davidcarboni.cryptolite.Password Maven / Gradle / Ivy

Go to download

Good, clean, "right" cryptography, bypassing all the options. See: http://www.daemonology.net/blog/2009-06-11-cryptographic-right-answers.html

There is a newer version: 1.3.3
Show newest version
package com.github.davidcarboni.cryptolite;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;

import java.security.Key;
import java.util.Arrays;

/**
 * This class provides password hashing and verification. The returned hashes
 * consist of the password hash, prepended with a random salt value. In order to
 * verify a password, the plaintext password should be passed to
 * {@link #verify(String, String)} along with the stored value originally
 * produced by {@link #hash(String)}.
 *
 * This password hashing and verification is done in the same way as Jasypt, but
 * uses {@value #ALGORITHM}, rather than MD5.
 *
 * @author David Carboni
 */
public class Password {

    /**
     * The password hashing function.
     */
    public static final String ALGORITHM = "PBKDF2WithHmacSHA1";

    /**
     * The iteration count for the function.
     */
    public static final int ITERATION_COUNT = 1024;

    /**
     * The number of bytes to produce in the hash.
     */
    public static final int HASH_SIZE = 256;

    /**
     * Produces a good hash of the given password, using {@value #ALGORITHM}, an
     * iteration count of {@value #ITERATION_COUNT} and a random salt value of
     * {@value Random#SALT_BYTES} bytes. The returned value is a concatenation of the
     * salt value and the password hash and this should be passed as returned to
     * {@link #verify(String, String)} along with the plaintext password.
     *
     * @param password The password to be hashed.
     * @return The password hash as a base-64 encoded String. If the given
     * password is null, null is returned.
     */
    public static String hash(String password) {

        String result = null;

        if (password != null) {

            // Generate a random salt:
            String salt = Random.salt();

            // Hash the password:
            byte[] hash = hash(password, salt);

            // Concatenate the salt and hash:
            byte[] concatenated = ArrayUtils.addAll(ByteArray.fromBase64String(salt), hash);

            // Base-64 encode the result:
            result = ByteArray.toBase64String(concatenated);
        }

        return result;
    }

    /**
     * Verifies the given plaintext password against a value that
     * {@link #hash(String)} produced.
     *
     * @param password A plaintext password. If this is null, false will be returned.
     * @param hash     A value previously produced by {@link #hash(String)}. If this
     *                 is empty or shorter than expected, false will be returned.
     * @return If the password hashes to the same value as that contained in the
     * hash parameter, true.
     */
    public static boolean verify(String password, String hash) {

        boolean result = false;

        if (StringUtils.isNotBlank(hash) && password != null) {
            // Get the salt and hash from the input string:
            byte[] bytes = ByteArray.fromBase64String(hash);

            // Check the size of the value to ensure it's at least as long as
            // the salt:
            if (bytes.length >= Random.SALT_BYTES) {

                // Extract the salt and password hash:
                String salt = getSalt(bytes);
                byte[] existingHash = getHash(bytes);

                // Hash the password with the same salt in order to get the same
                // result:
                byte[] comparisonHash = hash(password, salt);

                // See whether they match:
                result = Arrays.equals(existingHash, comparisonHash);
            }
        }

        return result;
    }

    /**
     * This method does the actual work of hashing a plaintext password string,
     * using {@link Keys#generateSecretKey(char[], String, int)}.
     *
     * @param password The plaintext password.
     * @param salt     The salt value to use in the hash.
     * @return The hash of the password.
     */
    private static byte[] hash(String password, String salt) {

        Key key = Keys.generateSecretKey(toCharArray(password), salt, HASH_SIZE);
        return key.getEncoded();
    }

    /**
     * Converts the given password to a char array.
     *
     * NB: an empty char array can cause errors when passed to
     * {@link javax.crypto.SecretKeyFactory#generateSecret(java.security.spec.KeySpec)}
     * in {@link Keys#generateSecretKey(char[], String, int)}, so if the
     * password is an empty String, the return value is a char array containing
     * a single element of value 0.
     *
     * @param password The password to be converted.
     * @return {@link String#toCharArray()} or, if the password is an empty
     * string new char[] {0}
     */
    private static char[] toCharArray(String password) {
        char[] result = password.toCharArray();
        if (result.length == 0) {
            result = new char[]{0};
        }
        return result;
    }

    /**
     * Retrieves the salt from the given value.
     *
     * @param value The overall password hash value.
     * @return The salt, which is the first {@value Random#SALT_BYTES} bytes of the
     */
    private static String getSalt(byte[] value) {

        byte[] salt = new byte[Random.SALT_BYTES];
        System.arraycopy(value, 0, salt, 0, salt.length);
        return ByteArray.toBase64String(salt);
    }

    /**
     * Retrieves the hash from the given value.
     *
     * @param value The overall password hash value.
     * @return The salt, which is the first {@value Random#SALT_BYTES} bytes of the
     */
    private static byte[] getHash(byte[] value) {

        byte[] hash = new byte[value.length - Random.SALT_BYTES];
        System.arraycopy(value, Random.SALT_BYTES, hash, 0, hash.length);
        return hash;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy