crypto.encryptor.util.HashFunctionUtil.java Source code

Java tutorial

Introduction

Here is the source code for crypto.encryptor.util.HashFunctionUtil.java

Source

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package crypto.encryptor.util;

import crypto.encryptor.enums.HashFunctionEnum;
import static crypto.encryptor.util.EncryptorUtil.UTF8;
import java.io.UnsupportedEncodingException;
import java.util.List;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.util.ArrayList;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.util.encoders.Base64;
import org.mindrot.jbcrypt.BCrypt;

/**
 *
 * @author Samuel Huang
 * Date: 13/04/2016
 */
public final class HashFunctionUtil {

    private static List<HashFunctionEnum> unsupportedHashFunctionList = new ArrayList<HashFunctionEnum>();

    // Define the BCrypt workload to use when generating password hashes. 10-31 is a valid value.
    private static final int BCRYPT_WORKLOAD = 12;

    // More hash functions will be added in future. Right now the only supported hash functions are
    // MD5, SHA-1, SHA-256, BCrypt

    /**
     * Hashing food for thoughts:
     * - http://howtodoinjava.com/security/how-to-generate-secure-password-hash-md5-sha-pbkdf2-bcrypt-examples/
     * - http://www.mindrot.org/projects/jBCrypt/
     * - https://www.owasp.org/index.php/Hashing_Java
     * - https://crackstation.net/hashing-security.htm
     * - https://www.cigital.com/blog/proper-use-of-javas-securerandom/
     */

    static {
        // Add unsupported hash functions in list here
        // unsupportedHashFunctionList.add( HashFunctionEnum.BCRYPT );
    }

    private static final Logger logger = LogManager.getLogger();

    /**
     * A static method for hashing data with a chosen hash function depending on input parameters.
     * 
     * @param salt             the salt value
     * @param data             the data to hash
     * @param hashFunctionEnum the enum representing hash function
     * @return                 the hashed data as Base64 encoded string
     */
    public static String hash(String salt, String data, HashFunctionEnum hashFunctionEnum)
            throws UnsupportedEncodingException {

        logger.debug(
                "Hashing data with key='" + salt + "', value='" + data + "', Hash function=" + hashFunctionEnum);

        if (hashFunctionEnum == null) {
            throw new IllegalArgumentException(
                    "Input argument 'hashFunctionEnum' of " + "type HashFunctionEnum can't be null ");
        }

        if (unsupportedHashFunctionList.contains(hashFunctionEnum)) {
            throw new UnsupportedOperationException(
                    "The hash function " + hashFunctionEnum.value() + " is not supported.");
        }

        String hashedString = null;
        if (hashFunctionEnum == HashFunctionEnum.BCRYPT) {

            hashedString = bCryptHashPassword(data, salt);

        } else {

            MessageDigest messageDigest = null;
            try {
                messageDigest = MessageDigest.getInstance(hashFunctionEnum.value());
            } catch (Exception ex) {
                ex.printStackTrace();
                logger.error("Error hashing data by Hash function '" + hashFunctionEnum.name() + "'", ex);
                return null;
            }

            messageDigest.update(salt.getBytes());
            messageDigest.update(data.getBytes());
            byte[] hashedBytes = messageDigest.digest();

            // Use this if need to output byte as HEX encoded string. 
            //
            // The use of DatatypeConverter.printHexBinary(..) is gold, see 
            // https://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to-a-hex-string-in-java for why.
            // return javax.xml.bind.DatatypeConverter.printHexBinary( bytesArray );

            hashedString = new String(Base64.encode(hashedBytes), UTF8);
        }

        return hashedString;
    }

    /**
     * 
     * This method can be used to generate a string representing a hashed password
     * suitable for storing in a database. It will be an OpenBSD-style crypt(3) formatted
     * hash string of length=60.
     * 
     * The bcrypt workload (i.e. BCRYPT_WORKLOAD) is specified in the above static variable, a value from 10 to 31.
     * A workload of 12 is a very reasonable safe default as of 2013.
     * 
     * This automatically handles secure 128-bit salt generation and storage within the hash.
     * 
     * @param plaintextPassowrd the plaintext password to hash
     * @param salt              salt for BCrypt hash function. Use getBCryptSalt() to return dedicated strong salt for BCrypt hash.
     * @return String           a string of length 60 that represents the BCrypt hashed password
     */
    public static String bCryptHashPassword(String plaintextPassowrd, String salt) {
        return BCrypt.hashpw(plaintextPassowrd, salt);
    }

    public static String getBCryptSalt() {
        return BCrypt.gensalt(BCRYPT_WORKLOAD);
    }

    public static boolean isHashFunctionSupported(HashFunctionEnum hashFunctionEnum) {
        if (hashFunctionEnum == null) {
            return false;
        }
        return !unsupportedHashFunctionList.contains(hashFunctionEnum);
    }

    /**
     * Return random generated salt. This should be a pretty good salt. See  
     * https://www.cigital.com/blog/proper-use-of-javas-securerandom/ for why.
     * 
     * @return Base64 encoded salt for hashing data.
     * 
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws UnsupportedEncodingException 
     */
    public static String getSalt()
            throws NoSuchAlgorithmException, NoSuchProviderException, UnsupportedEncodingException {
        // Always specify the exact PRNG and provider that you wish to use else may end up with weaker implementation
        // of PRNG in different platforms
        SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG", "SUN");

        byte[] saltBytes = new byte[16];

        // always call SecureRandom.nextBytes(byte[]) immediately after creating a new instance of the PRNG
        secureRandom.nextBytes(saltBytes);

        // return base64 encoded string
        return new String(Base64.encode(saltBytes), UTF8);
    }
}