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

119,454

Solution 1

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();
  }
}

Solution 2

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

Solution 3

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>
Share:
119,454

Related videos on Youtube

Louis Hong
Author by

Louis Hong

Updated on July 09, 2022

Comments

  • Louis Hong
    Louis Hong almost 2 years

    I've been looking around and the closest answer is : How to generate a random alpha-numeric string?

    I want to follow this workflow according to this CrackStation tutorial:

    To Store a Password

    1. Generate a long random salt using a CSPRNG.

    2. Prepend the salt to the password and hash it with a standard cryptographic hash function such as SHA256.

    3. Save both the salt and the hash in the user's database record.

    To Validate a Password

    1. Retrieve the user's salt and hash from the database.

    2. Prepend the salt to the given password and hash it using the same hash function.

    3. Compare the hash of the given password with the hash from the database. If they match, the password is correct. Otherwise, the password is incorrect.

    I don't know how to generate a SALT. I figured out how to generate a hash using the MessageDigest. I tried using SecureRandom but nextByte method produces garbled code.

    Edit: I don't know which answer to choose, they're too complicated for me, I have decided to use jBCrypt; jBCript is easy to use, does all the complex stuff behind the scenes. so I'll let the community vote up for the best answer.

    • NiziL
      NiziL almost 11 years
      If you want an easy way to encrypt a password in java, look at jBcrypt :)
    • Louis Hong
      Louis Hong almost 11 years
      @Nizil Thank you! This is what I'm probably going to use now.
    • Raunak Agarwal
      Raunak Agarwal almost 11 years
      @Louie Though it looks really simple but using a certified Security Provider would be the right thing to do.
    • NiziL
      NiziL almost 11 years
      @Louie You're welcome :) I really like this librairy, simple and efficient, moreover blowflish algorithm is perfect for password encryption :D
    • Syon
      Syon almost 11 years
      +1 for jBcrypt. It's well known and respected. Don't implement your own stuff unless you really know what you're doing, it's just too easy to mess up.
    • Louis Hong
      Louis Hong about 10 years
      Guys It's been so long I don't know which one is right anymore. You guys can vote, but I won't choose the "right answer"
    • Mike Ounsworth
      Mike Ounsworth about 6 years
      jBcrypt looks like a good library, but slight correction to @NiziL about language: "password hashing" is not "encryption". Encryption is where you can use a key to reverse it and get back the data (like database or hard drive encryption). jBcrypt is hashing, not encryption, be careful not to add to the confusion around these terms.
    • NiziL
      NiziL about 6 years
      @MikeOunsworth I've never been very good with words... and security :P Thanks for pointing out this mistake !
  • Louis Hong
    Louis Hong almost 11 years
    Am I generating the SALT the right way? I printed it out only because wanted to know if it was valid.
  • Raunak Agarwal
    Raunak Agarwal almost 11 years
    Anyways you would need to save salt used for each password otherwise, you won't be able to authenticate the password
  • Louis Hong
    Louis Hong almost 11 years
    So although the encoding is wrong, I can still just assume it's going to append to my password correctly and generate my hash?
  • Raunak Agarwal
    Raunak Agarwal almost 11 years
    I have updated my answer with some more sample of how you could hash the password. Look, I am using the bytes of the salt and not the encoded value. So, salt encoding would hardly matter. Also, Base64 is considered to be a standard encoding for encrypted or hashed string which has some of associated advantages.
  • Louis Hong
    Louis Hong almost 11 years
    Where did you get the class Base64 from?
  • Raunak Agarwal
    Raunak Agarwal almost 11 years
  • Louis Hong
    Louis Hong almost 11 years
    is this safe enough, this generates alphanumeric String?commons.apache.org/proper/commons-lang/javadocs/api-3‌​.1/org/…
  • Raunak Agarwal
    Raunak Agarwal almost 11 years
    If you are worried about using external library. You could use java's internal base64encoder docs.oracle.com/cd/E23507_01/Platform.20073/apidoc/atg/secur‌​ity/…
  • Louis Hong
    Louis Hong almost 11 years
    I'm going to try this out, but it's hard for me to determine if using the methods from apache common is better or pure native Java is better. Any suggestions?
  • Louis Hong
    Louis Hong almost 11 years
    Do I store the bytes as a BLOB in SQL?
  • assylias
    assylias almost 11 years
    @Louie I suppose it depends on which database you use but BLOB seems appropriate. You can ask a separate question on that specific point if you need advice.
  • Syon
    Syon almost 11 years
    @Raunak Agarwal: Fyi, salt does not prevent dictionary attacks (using a dictionary of words to guess a password), it prevents attacks using rainbow tables, and stops all identical passwords from being cracked at the same time. Also, Base64 and UTF-8 are not quite comparable. UTF-8 is a string encoding scheme, it's a binary format for representing human readable text. Base64 is a byte encoding scheme, it's a text format for representing binary data.
  • Raunak Agarwal
    Raunak Agarwal almost 11 years
    @Syon: FYI, Rainbow table is nothing more than a refined name for 'Pre-computed dictionary attack' and salt is used to protect against these kind of attacks. Secondly, base64 and UTF-8 have their own utility and right now, I am suggesting OP to use base64 instead of UTF-8 for the reasons I have mentioned above. I hope you understand. :)
  • Syon
    Syon almost 11 years
    @Raunak Agarwal: Respectfully, incorrect on both counts. A dictionary attack may or may not be pre-computed. A rainbow table is a specialized storage system for pre-computed hashes, it may contain hashes computed via a dictionary and/or brute-force, but does not have to contain dictionary data. Salt protects only against pre-compute attacks, and dictionary does not fall exclusively in to that category.
  • Syon
    Syon almost 11 years
    @Raunak Agarwal: As for Base64, I certainly understand why you recommend it (and that's fine), but you also stated "instead of using UTF-8 encoding you may want to use Base64". These two encoding types are not interchangeable. Base64 is UTF-8/ASCII encoded data.
  • Eran Medan
    Eran Medan about 7 years
    @assylias first, thank you! two questions - 1: is it still relevant in 2017? (libsodium / Argon2?) 2: why on earth isn't there an apache commons library for something that is so commonly done wrong by developers and every single application that has any form of authentication needs? :)
  • Farhad Abdolhosseini
    Farhad Abdolhosseini over 5 years
    thanks for this. isExpectedPassword can probably just "return Arrays.equals(pwdHash, expectedHash)", right? and to confirm my understanding, this method is safe from a timing attack because it's using the hashed values as opposed to the plain text?
  • assylias
    assylias over 5 years
    @FarhadAbdolhosseini Yes indeed, that would work. Regarding timing attack, I am not 100% sure about timing attack possibility (yes hashed values are used but I don't know if the hashing algo runs in constant time).
  • Farhad Abdolhosseini
    Farhad Abdolhosseini over 5 years
    @assylias I would think because of the iterations that the hashing algos time should be negligible. My concern was with isExpectedPassword returning at different times for different outputs given the return statements. e.g. if i try multiple inputs and see that it returns false later for one of them, then I may be able to assume that this one meets the length condition but then fails the for loop (e.g. if i knew/assumed the code), which I could then exploit. But since they're hashed, i could only learn about the hashed values, which don't give me info on the actual values, so should be fine.