Olu
Olu

Reputation: 77

How to validate user password after hashing using sha-256 salted in Java

I am new to Java; so also postgresql I am using for the backend. I want to hash user password and save in the database using sha-256 Next time the user visits, I want to compare the password user submitted with the saved hashpassword, "at the java end". I have tried this (I got from the internet):

private static String get_SHA_512_SecurePassword(String passwordToHash, byte[] salt) {
        
    String generatedPassword = null;
    try {
        MessageDigest md = MessageDigest.getInstance("SHA-512");
        md.update(salt);
        byte[] bytes = md.digest(passwordToHash.getBytes());
        StringBuilder sb = new StringBuilder();
        for(int i=0; i< bytes.length ;i++)
        {
            sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1));
        }
        generatedPassword = sb.toString();
    } 
    catch (NoSuchAlgorithmException e) 
    {
        e.printStackTrace();
    }
    return generatedPassword;
}
    

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

public static void main(String[] args) {
    //do some things ....
    System.out.println("\nEnter your password");
    String password = scanner.nextLine();

    try {
        byte[] salt = getSalt();
        securePassword = get_SHA_512_SecurePassword(password, salt); // This is saved in the database.
        System.out.println(securePassword);
    
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    }

    //Now ask user to enter his password
    System.out.println("Enter your password");
    String password1 = scanner.nextLine();

    //validate user password. Compare password with password1
    try {
        byte[] salt = getSalt();
        securePassword1 = get_SHA_512_SecurePassword(password1, salt);
        System.out.println(securePassword1);
    
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    }

    //I believe this is wrong but I don't have alternative
    if (securePassword.equals(securePassword1)) {
        System.out.println("Both are equal");
    } else {
        System.out.println("Both are not equal");
    }
}

Even when both passwords are same, the comparison fails. The result is same "Both are not equal" whether both passwords are same or different. So is there a method of validating the user password? I have searched. The solution I got is to use Spring Bcript which I don't want for now. Another alternative I am pushing to future is to do verification at the backend (as is the practise with php-Mysqli password_verify($password, $passwodhashed)). Reason is I am using postgresql with java, both new to me. Thank you

Upvotes: 3

Views: 5983

Answers (4)

olin00
olin00

Reputation: 1

Basically what @jccampanero wrote is correct but does not highlight the issue in your code: Your passwords don't match because the salt variable that you pass into the hashing function is generated 2x as a random number. Therefore the hash for the first password contains salt1 and hash for the second password contains salt2 (salt 1 and 2 are different values). If you use the same salt value then the passwords will match.

Upvotes: 0

jccampanero
jccampanero

Reputation: 53391

Your code looks fine (well, be aware that you are using SHA-512 not SHA-256 as the question suggest) but please, take into account that you need to store the salt as well as the obtained hash in the database in order to validate the user password later.

Salts are used in hashes as a way to protect against password identification, i.e., in order to prevent that applying the same hash algorithm to the same password for two different subjects gave as a result the same hash.

Basically, you hash the result of concatenating the generated random salt and the user provided password: as mentioned, to validate the password later you need to store in the database the password as well.

In your test you should always use the same salt:

public static void main(String[] args) {
    //do some things ....
    System.out.println("\nEnter your password");
    String password = scanner.nextLine();

    // The salt should be the same every time you generate the hash
    byte[] salt = getSalt();

    try {
        securePassword = get_SHA_512_SecurePassword(password, salt); // This is saved in the database.
        System.out.println(securePassword);
    
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    }

    //Now ask user to enter his password
    System.out.println("Enter your password");
    String password1 = scanner.nextLine();

    //validate user password. Compare password with password1
    try {
        securePassword1 = get_SHA_512_SecurePassword(password1, salt);
        System.out.println(securePassword1);
    
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    }

    //I believe this is wrong but I don't have alternative
    if (securePassword.equals(securePassword1)) {
        System.out.println("Both are equal");
    } else {
        System.out.println("Both are not equal");
    }
}

A word of caution, though.

The proposed approach has several disadvantages, among others:

  • As you indicated in your comment, you need to manage salts in addition to the password themselves.
  • The SHA-* message digest algorithms are more suitable for general purpose hashing.

For these and other reasons, especially to improve security, to deal with password hashing exist a whole family of well khown algorithms such as bcrypt, PBKDF2, or the more resource expensive scrypt, to name some of them.

Please, prefer the use of any of these algorithms and, if possible, use a battle tested and widely used implementation such as the one provided by BouncyCastle - even in these libraries you can find flaws. For instance:

import java.security.SecureRandom;

import org.bouncycastle.crypto.generators.OpenBSDBCrypt;

public class BCryptPasswordHashing {

  private static final SecureRandom  secureRandom = new SecureRandom();

  private final int costFactor;

  /**
   * Creates a new instance of <code>BCryptPasswordHashing</code>.
   *
   * @param costFactor the BCrypt cost factor, must be between 4 and 31, inclusive.
   *                   The greatest this value, the greatest hashing strength, but
   *                   more computational power as well.
   */
  public BCryptPasswordHashing(int costFactor) {
    this.costFactor = costFactor;
  }

  public final String hashPassword(char[] password) {
    if (password == null) {
      return null;
    }

    final byte[] salt = this.getSalt();
    final String bCryptHash = OpenBSDBCrypt.generate(password, salt, this.costFactor);
    return bCryptHash;
  }

  public final boolean verifyPassword(char[] password, String storedPassword) {
    if (password == null) {
      return (storedPassword == null);
    }

    if (storedPassword == null) {
      return false;
    }

    return OpenBSDBCrypt.checkPassword(storedPassword, password);
  }

  private byte[] getSalt() {
    byte[] salt = new byte[16]; // BCrypt salt size
    secureRandom.nextBytes(salt);
    return salt;
  }
}

You can test this code with the following main method:

public static void main(String[] args) {
  //do some things ....
  System.out.println("\nEnter your password");
  String password = scanner.nextLine();
  
  // In my opinion, a cost factor between 12 and 16 is ok
  // Please, consider read: https://labs.clio.com/bcrypt-cost-factor-4ca0a9b03966 
  final BCryptPasswordHashing bCryptPasswordHashing = new BCryptPasswordHashing(12);
  final String bCryptHash = bCryptPasswordHashing.hashPassword(password.toCharArray());
  
  //Now ask user to enter his password
  System.out.println("Enter your password");
  String password1 = scanner.nextLine();

  final boolean passwordMatch = bCryptPasswordHashing.verifyPassword(password1.toCharArray(), bCryptHash);

  if (passwordMatch) {
    System.out.println("Both are equal");
  } else {
    System.out.println("Both are not equal");
  }
}

Although my recommendation had been to use a battle tested library, for its simplicity, it is worth value to mention the different algorithms implementations provided by Patrick Favre. Consider for instance this of BCrypt.

Upvotes: 1

Giddy Naya
Giddy Naya

Reputation: 4658

You've two options to pass validation:

  1. Use a constant value for salt.
  2. Use a dynamic value for salt but maintain a logic where the value of salt is the same for both password1 (The user's password in db) and password2 (The password to compare with).

Upvotes: 0

rzwitserloot
rzwitserloot

Reputation: 102902

I want to hash user password and save in the database using sha-256

Why? This is not industry standard. It is in fact an industry non-standard: A known bad thing. SHA-256 is optimized to run a few billion a second. There is active pressure on creating cheap rigs that can go as fast as possible what with all the cryptocurrencies flying around.

billions. You can scan a single hash for all variations including a capital and a special symbol for the entire english dictionary in less than a second.

What you want is a hash algorithm that is hard to create optimized hardware for - that your general purpose IAAS server CPU can do about as fast as custom code on a graphics card, or faster. Because a giant rig of a ton of full blown CPUs is a heck of a lot more expensive than an array of graphics cards, and harder to get your hands on.

In other words, you want a password hashing algorithm. There are many; the oldest and still perfectly suitable (in rather marked contrast to the SHA-and-co world, where SHA1 used to rule the roost, but that's at this point no longer advised for just about anything, by anybody), is BCrypt.

There are others: SCrypt, PBKDF, and more.

Pick any one of those. Forget SHA-512.

Your second problem is salts. The point of them is that you store them. They are randomly generated and are required to check the hash later. The algorithm you run when a user enters a new password is this:

  • Generate a salt with a random number generator.
  • Hash up the concatenation of the salt and that password.
  • Store both the salt and that hash result in your DB.

Then, when a user enters their password again later you need to verify it, like this:

  • The user entered username and password.
  • Look up the salt in your DB based on that username.
  • Concatenate the salt and the password they just entered, and hash it.
  • If it is identical to the result value in your DB row - that was the password. If not it wasn't.

This API in the end boils down to something quite simple:

/**
 * @return This method returns voodoo magic. You don't need to know what it is, just store it, and return it unchanged later when you call verify. */
public String createHash(String password) {
   ... implementation here ...
}

public boolean verify(String password, String thatThingIGaveYouEarlier) {
   ... implementation here ...
}

Because that is simple and hard to mess up, that is what your password hashing system should work with. Not what you're doing: Hand-managing the salting, the hashing, and the verifying.

NB: Just to satisfy a curiosity you might have, that string it returns is an encoded thing that contains only safe characters and contains both the salt and the hash and some extra placeholders that even make it compatible with other, non-java-written libraries.

Fortunately, all of the good password hash implementations for java work just like that. That's great - it's easy to use. It also means you get to enjoy your current 'use one column for storing this stuff'. It's just that this column will contain a string that contains all the relevant information (hash and salt).

Here is an entire tutorial about it if you like a second source.

Here is the Java BCrypt implementation, with the above API. Yes, that site looks old, and mentions 2015 as the last update. It's not because it isn't being maintained, it's because BCrypt has been the go-to answer, without any relevant attacks or issues, for a decade. This is good news, for security. It's a single java file, with a very permissive open source license. Just paste it in your project and go, if you prefer that over adding it to your build system's dependency list.

Upvotes: 1

Related Questions