Reputation: 644
I've got a web application and I want to store password hashes in a remote database. I wrote a simple class, which generates a password hash. Two public methods are available: 1. encrypt - encrypts the password and creates hash 2. checkPassword - calls encrypt to create hash, then compares two hashes
I made a simple test with two password checks, one should pass, another shouldn't.
Sometimes it works, sometimes don't.
Correct output:
password: abcdefg, hash: fd9927e15150bd01713115a761d1dea18b7da4aa
password: abcdefg, salt: 3595ac1baff6aa5e0097520593c7ac74
password: abcdefg, hash: fd9927e15150bd01713115a761d1dea18b7da4aa
password: abcdefg, salt: 3595ac1baff6aa5e0097520593c7ac74
passwords: abcdefg and abcdefg matched: true
password: abcdefgh, hash: a64a2958f3999d8ecdeb03326a151e786435ea4
password: abcdefgh, salt: 3595ac1baff6aa5e0097520593c7ac74
passwords: abcdefg and abcdefgh matched: false
Wrong output:
password: abcdefg, hash: 4913fe5cdea3346690463f76f73c1336ae976674
password: abcdefg, salt: 8e2aa1ec28d84fbaf78a6df260a7c707
password: abcdefg, hash: 97abd26927bf96076019b932bf6ab5494a8b0979
password: abcdefg, salt: 8e2aa1ec28d84fbaf78a6df260a7c707
passwords: abcdefg and abcdefg matched: false
password: abcdefgh, hash: 70594cd854bd60e07dfe14f72f01aa1f50de9aa2
password: abcdefgh, salt: 8e2aa1ec28d84fbaf78a6df260a7c707
passwords: abcdefg and abcdefgh matched: false
Source:
import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Random;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
public class CryptoUtils {
/***************************************************************************
* @param password
*
* @return String[2] { hashed password, salt }
*
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
***************************************************************************/
public static String[] encrypt(String password) throws NoSuchAlgorithmException, InvalidKeySpecException {
byte[] salt = new byte[16];
new Random().nextBytes(salt);
return encrypt(password, salt);
}
private static String[] encrypt(String password, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException {
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 2048, 160);
SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] hash = f.generateSecret(spec).getEncoded();
String passHash = new BigInteger(1, hash).toString(16);
String saltString = new BigInteger(1, salt).toString(16);
System.out.println("password: " + password + ", hash: " + passHash);// DEBUG
System.out.println("password: " + password + ", salt: " + saltString);
return new String[] { passHash, saltString };
}
public static boolean checkPassword(String password, String hash, String salt) throws NoSuchAlgorithmException, InvalidKeySpecException {
String[] encrypted = encrypt(password, new BigInteger(salt, 16).toByteArray());
return encrypted[0].equals(hash);
}
public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException {
String pass1 = "abcdefg";
String pass2 = pass1;
String pass3 = pass1 + "h";
String[] result = encrypt(pass1);
String hash = result[0];
String salt = result[1];
System.out.println("passwords: " + pass1 + " and " + pass2 + " matched: " + checkPassword(pass2, hash, salt));
System.out.println("passwords: " + pass1 + " and " + pass3 + " matched: " + checkPassword(pass3, hash, salt));
}
}
Can anybody help?
Upvotes: 1
Views: 2196
Reputation: 644
The problem was in String to byte array and back conversion. Now I use Base64.encode() and Base64.decode() methods and it works.
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Random;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
public class CryptoUtils {
/***************************************************************************
* @param password
*
* @return String[2] { hashed password, salt }
*
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
***************************************************************************/
public static String[] encrypt(String password) throws NoSuchAlgorithmException, InvalidKeySpecException {
byte[] salt = new byte[16];
new Random().nextBytes(salt);
return encrypt(password, salt);
}
private static String[] encrypt(String password, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException {
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 2048, 160);
SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] hash = f.generateSecret(spec).getEncoded();
String passHash = Base64.encode(hash);
String saltString = Base64.encode(salt);
System.out.println("password: " + password + ", hash: " + passHash);// DEBUG
System.out.println("password: " + password + ", salt: " + saltString);
return new String[] { passHash, saltString };
}
public static boolean checkPassword(String password, String hash, String salt) throws NoSuchAlgorithmException, InvalidKeySpecException {
String[] encrypted = encrypt(password, Base64.decode(salt));
return encrypted[0].equals(hash);
}
public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException {
String pass1 = "abcdefg";
String pass2 = pass1;
String pass3 = pass1 + "h";
String[] result = encrypt(pass1);
String hash = result[0];
String salt = result[1];
System.out.println("passwords: " + pass1 + " and " + pass2 + " matched: " + checkPassword(pass2, hash, salt));
System.out.println("passwords: " + pass1 + " and " + pass3 + " matched: " + checkPassword(pass3, hash, salt));
}
}
Upvotes: 0
Reputation: 8865
The problem is your conversion of salt string to bytes using BigInteger. If your salt string is negative and doesn't start with a zero bit, this code works. If your salt string is positive, BigInteger.toByteArray() has to sign extend by adding one more zero byte to the beginning, so it ends up 17 bytes long. Also, if your salt string has a zero as its highest-order byte, BigInteger.toByteArray() doesn't need 16 bytes to represent it so again your salt ends up the wrong length. You can probably write logic to reformat BigInteger's output to always contain 16 bytes, but it might be easier to simply parse the input string two characters at a time, adding byte values to an array yourself.
Upvotes: 2