How do I generate a SALT in Java for Salted-Hash? How do I generate a SALT in Java for Salted-Hash? java java

How do I generate a SALT in Java for Salted-Hash?


Inspired from this post and that post, I use this code to generate and verify hashed salted passwords. It only uses JDK provided classes, no external dependency.

The process is:

  • you create a salt with getNextSalt
  • you ask the user his password and use the hash method to generate a salted and hashed password. The method returns a byte[] which you can save as is in a database with the salt
  • to authenticate a user, you ask his password, retrieve the salt and hashed password from the database and use the isExpectedPassword method to check that the details match
/** * A utility class to hash passwords and check passwords vs hashed values. It uses a combination of hashing and unique * salt. The algorithm used is PBKDF2WithHmacSHA1 which, although not the best for hashing password (vs. bcrypt) is * still considered robust and <a href="https://security.stackexchange.com/a/6415/12614"> recommended by NIST </a>. * The hashed value has 256 bits. */public class Passwords {  private static final Random RANDOM = new SecureRandom();  private static final int ITERATIONS = 10000;  private static final int KEY_LENGTH = 256;  /**   * static utility class   */  private Passwords() { }  /**   * Returns a random salt to be used to hash a password.   *   * @return a 16 bytes random salt   */  public static byte[] getNextSalt() {    byte[] salt = new byte[16];    RANDOM.nextBytes(salt);    return salt;  }  /**   * Returns a salted and hashed password using the provided hash.<br>   * Note - side effect: the password is destroyed (the char[] is filled with zeros)   *   * @param password the password to be hashed   * @param salt     a 16 bytes salt, ideally obtained with the getNextSalt method   *   * @return the hashed password with a pinch of salt   */  public static byte[] hash(char[] password, byte[] salt) {    PBEKeySpec spec = new PBEKeySpec(password, salt, ITERATIONS, KEY_LENGTH);    Arrays.fill(password, Character.MIN_VALUE);    try {      SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");      return skf.generateSecret(spec).getEncoded();    } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {      throw new AssertionError("Error while hashing a password: " + e.getMessage(), e);    } finally {      spec.clearPassword();    }  }  /**   * Returns true if the given password and salt match the hashed value, false otherwise.<br>   * Note - side effect: the password is destroyed (the char[] is filled with zeros)   *   * @param password     the password to check   * @param salt         the salt used to hash the password   * @param expectedHash the expected hashed value of the password   *   * @return true if the given password and salt match the hashed value, false otherwise   */  public static boolean isExpectedPassword(char[] password, byte[] salt, byte[] expectedHash) {    byte[] pwdHash = hash(password, salt);    Arrays.fill(password, Character.MIN_VALUE);    if (pwdHash.length != expectedHash.length) return false;    for (int i = 0; i < pwdHash.length; i++) {      if (pwdHash[i] != expectedHash[i]) return false;    }    return true;  }  /**   * Generates a random password of a given length, using letters and digits.   *   * @param length the length of the password   *   * @return a random password   */  public static String generateRandomPassword(int length) {    StringBuilder sb = new StringBuilder(length);    for (int i = 0; i < length; i++) {      int c = RANDOM.nextInt(62);      if (c <= 9) {        sb.append(String.valueOf(c));      } else if (c < 36) {        sb.append((char) ('a' + c - 10));      } else {        sb.append((char) ('A' + c - 36));      }    }    return sb.toString();  }}


You were right regarding how you want to generate salt i.e. its nothing but a random number. For this particular case it would protect your system from possible Dictionary attacks. Now, for the second problem what you could do is instead of using UTF-8 encoding you may want to use Base64. Here, is a sample for generating a hash. I am using Apache Common Codecs for doing the base64 encoding you may select one of your own

public byte[] generateSalt() {        SecureRandom random = new SecureRandom();        byte bytes[] = new byte[20];        random.nextBytes(bytes);        return bytes;    }public String bytetoString(byte[] input) {        return org.apache.commons.codec.binary.Base64.encodeBase64String(input);    }public byte[] getHashWithSalt(String input, HashingTechqniue technique, byte[] salt) throws NoSuchAlgorithmException {        MessageDigest digest = MessageDigest.getInstance(technique.value);        digest.reset();        digest.update(salt);        byte[] hashedBytes = digest.digest(stringToByte(input));        return hashedBytes;    }public byte[] stringToByte(String input) {        if (Base64.isBase64(input)) {            return Base64.decodeBase64(input);        } else {            return Base64.encodeBase64(input.getBytes());        }    }

Here is some additional reference of the standard practice in password hashing directly from OWASP


Another version using SHA-3, I am using bouncycastle:

The interface:

public interface IPasswords {    /**     * Generates a random salt.     *     * @return a byte array with a 64 byte length salt.     */    byte[] getSalt64();    /**     * Generates a random salt     *     * @return a byte array with a 32 byte length salt.     */    byte[] getSalt32();    /**     * Generates a new salt, minimum must be 32 bytes long, 64 bytes even better.     *     * @param size the size of the salt     * @return a random salt.     */    byte[] getSalt(final int size);    /**     * Generates a new hashed password     *     * @param password to be hashed     * @param salt the randomly generated salt     * @return a hashed password     */    byte[] hash(final String password, final byte[] salt);    /**     * Expected password     *     * @param password to be verified     * @param salt the generated salt (coming from database)     * @param hash the generated hash (coming from database)     * @return true if password matches, false otherwise     */    boolean isExpectedPassword(final String password, final byte[] salt, final byte[] hash);    /**     * Generates a random password     *     * @param length desired password length     * @return a random password     */    String generateRandomPassword(final int length);}

The implementation:

import org.apache.commons.lang3.ArrayUtils;import org.apache.commons.lang3.Validate;import org.apache.log4j.Logger;import org.bouncycastle.jcajce.provider.digest.SHA3;import java.io.Serializable;import java.io.UnsupportedEncodingException;import java.security.SecureRandom;import java.util.ArrayList;import java.util.Arrays;import java.util.List;import java.util.Random;public final class Passwords implements IPasswords, Serializable {    /*serialVersionUID*/    private static final long serialVersionUID = 8036397974428641579L;    private static final Logger LOGGER = Logger.getLogger(Passwords.class);    private static final Random RANDOM = new SecureRandom();    private static final int DEFAULT_SIZE = 64;    private static final char[] symbols;    static {            final StringBuilder tmp = new StringBuilder();            for (char ch = '0'; ch <= '9'; ++ch) {                    tmp.append(ch);            }            for (char ch = 'a'; ch <= 'z'; ++ch) {                    tmp.append(ch);            }            symbols = tmp.toString().toCharArray();    }    @Override public byte[] getSalt64() {            return getSalt(DEFAULT_SIZE);    }    @Override public byte[] getSalt32() {            return getSalt(32);    }    @Override public byte[] getSalt(int size) {            final byte[] salt;            if (size < 32) {                    final String message = String.format("Size < 32, using default of: %d", DEFAULT_SIZE);                    LOGGER.warn(message);                    salt = new byte[DEFAULT_SIZE];            } else {                    salt = new byte[size];            }            RANDOM.nextBytes(salt);            return salt;    }    @Override public byte[] hash(String password, byte[] salt) {            Validate.notNull(password, "Password must not be null");            Validate.notNull(salt, "Salt must not be null");            try {                    final byte[] passwordBytes = password.getBytes("UTF-8");                    final byte[] all = ArrayUtils.addAll(passwordBytes, salt);                    SHA3.DigestSHA3 md = new SHA3.Digest512();                    md.update(all);                    return md.digest();            } catch (UnsupportedEncodingException e) {                    final String message = String                            .format("Caught UnsupportedEncodingException e: <%s>", e.getMessage());                    LOGGER.error(message);            }            return new byte[0];    }    @Override public boolean isExpectedPassword(final String password, final byte[] salt, final byte[] hash) {            Validate.notNull(password, "Password must not be null");            Validate.notNull(salt, "Salt must not be null");            Validate.notNull(hash, "Hash must not be null");            try {                    final byte[] passwordBytes = password.getBytes("UTF-8");                    final byte[] all = ArrayUtils.addAll(passwordBytes, salt);                    SHA3.DigestSHA3 md = new SHA3.Digest512();                    md.update(all);                    final byte[] digest = md.digest();                    return Arrays.equals(digest, hash);            }catch(UnsupportedEncodingException e){                    final String message =                            String.format("Caught UnsupportedEncodingException e: <%s>", e.getMessage());                    LOGGER.error(message);            }            return false;    }    @Override public String generateRandomPassword(final int length) {            if (length < 1) {                    throw new IllegalArgumentException("length must be greater than 0");            }            final char[] buf = new char[length];            for (int idx = 0; idx < buf.length; ++idx) {                    buf[idx] = symbols[RANDOM.nextInt(symbols.length)];            }            return shuffle(new String(buf));    }    private String shuffle(final String input){            final List<Character> characters = new ArrayList<Character>();            for(char c:input.toCharArray()){                    characters.add(c);            }            final StringBuilder output = new StringBuilder(input.length());            while(characters.size()!=0){                    int randPicker = (int)(Math.random()*characters.size());                    output.append(characters.remove(randPicker));            }            return output.toString();    }}

The test cases:

public class PasswordsTest {    private static final Logger LOGGER = Logger.getLogger(PasswordsTest.class);    @Before    public void setup(){            BasicConfigurator.configure();    }    @Test    public void testGeSalt() throws Exception {            IPasswords passwords = new Passwords();            final byte[] bytes = passwords.getSalt(0);            int arrayLength = bytes.length;            assertThat("Expected length is", arrayLength, is(64));    }    @Test    public void testGeSalt32() throws Exception {            IPasswords passwords = new Passwords();            final byte[] bytes = passwords.getSalt32();            int arrayLength = bytes.length;            assertThat("Expected length is", arrayLength, is(32));    }    @Test    public void testGeSalt64() throws Exception {            IPasswords passwords = new Passwords();            final byte[] bytes = passwords.getSalt64();            int arrayLength = bytes.length;            assertThat("Expected length is", arrayLength, is(64));    }    @Test    public void testHash() throws Exception {            IPasswords passwords = new Passwords();            final byte[] hash = passwords.hash("holacomoestas", passwords.getSalt64());            assertThat("Array is not null", hash, Matchers.notNullValue());    }    @Test    public void testSHA3() throws UnsupportedEncodingException {            SHA3.DigestSHA3 md = new SHA3.Digest256();            md.update("holasa".getBytes("UTF-8"));            final byte[] digest = md.digest();             assertThat("expected digest is:",digest,Matchers.notNullValue());    }    @Test    public void testIsExpectedPasswordIncorrect() throws Exception {            String password = "givemebeer";            IPasswords passwords = new Passwords();            final byte[] salt64 = passwords.getSalt64();            final byte[] hash = passwords.hash(password, salt64);            //The salt and the hash go to database.            final boolean isPasswordCorrect = passwords.isExpectedPassword("jfjdsjfsd", salt64, hash);            assertThat("Password is not correct", isPasswordCorrect, is(false));    }    @Test    public void testIsExpectedPasswordCorrect() throws Exception {            String password = "givemebeer";            IPasswords passwords = new Passwords();            final byte[] salt64 = passwords.getSalt64();            final byte[] hash = passwords.hash(password, salt64);            //The salt and the hash go to database.            final boolean isPasswordCorrect = passwords.isExpectedPassword("givemebeer", salt64, hash);            assertThat("Password is correct", isPasswordCorrect, is(true));    }    @Test    public void testGenerateRandomPassword() throws Exception {            IPasswords passwords = new Passwords();            final String randomPassword = passwords.generateRandomPassword(10);            LOGGER.info(randomPassword);            assertThat("Random password is not null", randomPassword, Matchers.notNullValue());    }}

pom.xml (only dependencies):

<dependencies>    <dependency>        <groupId>junit</groupId>        <artifactId>junit</artifactId>        <version>4.12</version>        <scope>test</scope>    </dependency>    <dependency>        <groupId>org.testng</groupId>        <artifactId>testng</artifactId>        <version>6.1.1</version>        <scope>test</scope>    </dependency>    <dependency>        <groupId>org.hamcrest</groupId>        <artifactId>hamcrest-all</artifactId>        <version>1.3</version>        <scope>test</scope>    </dependency>    <dependency>        <groupId>log4j</groupId>        <artifactId>log4j</artifactId>        <version>1.2.17</version>    </dependency>    <dependency>        <groupId>org.bouncycastle</groupId>        <artifactId>bcprov-jdk15on</artifactId>        <version>1.51</version>        <type>jar</type>    </dependency>    <dependency>        <groupId>org.apache.commons</groupId>        <artifactId>commons-lang3</artifactId>        <version>3.3.2</version>    </dependency></dependencies>