...
Code Block | ||
---|---|---|
| ||
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public final class Password {
private void setPassword(String pass) throws Exception {
byte[] salt = generateSalt(12);
MessageDigest msgDigest = MessageDigest.getInstance("SHA-256");
// Encode the string and salt
byte[] hashVal = msgDigest.digest((pass+salt).getBytes());
saveBytes(salt, "salt.bin");
// Save the hash value to password.bin
saveBytes(hashVal,"password.bin");
}
boolean checkPassword(String pass) throws Exception {
byte[] salt = loadBytes("salt.bin");
MessageDigest msgDigest = MessageDigest.getInstance("SHA-256");
// Encode the string and salt
byte[] hashVal1 = msgDigest.digest((pass+salt).getBytes());
// Load the hash value stored in password.bin
byte[] hashVal2 = loadBytes("password.bin");
return Arrays.equals(hashVal1, hashVal2);
}
private byte[] generateSalt(int n) {
// Generate a random byte array of length n
}
}
|
...
Code Block | ||
---|---|---|
| ||
import java.security.MessageDigestGeneralSecurityException; import java.security.SecureRandom; import java.security.NoSuchAlgorithmException; public .spec.KeySpec; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; final class Password { private void setPassword(byte[] pass) throws Exception { byte[] salt = generateSalt(12); byte[] input = appendArrays(pass, salt); MessageDigest msgDigest = MessageDigest.getInstance("SHA-256"); // Encode the string and saltSecureRandom random = new SecureRandom(); /* Set password to new value, zeroing out password */ void setPassword(char[] pass) throws IOException, GeneralSecurityException { byte[] hashValsalt = msgDigest.digest(input); clearArray(pass); new byte[12]; clearArrayrandom.nextBytes(inputsalt); saveBytes(salt, "salt.bin"); // Save the hash value to password.binbyte[] hashVal = encryptPassword( pass, salt); saveBytes(hashVal,"password.bin"); clearArray(saltArrays.fill(hashVal, (byte) 0); } clearArray(hashVal); } /* Indicates if given password is correct */ boolean checkPassword(bytechar[] pass) throws IOException, ExceptionGeneralSecurityException { byte[] salt = loadBytes("salt.bin"); byte[] inputhashVal1 = appendArraysencryptPassword( pass, salt); // MessageDigest msgDigestLoad the hash value stored in password.bin byte[] hashVal2 = MessageDigest.getInstanceloadBytes("SHA-256password.bin"); //boolean EncodearraysEqual the= stringtimingEquals( and salthashVal1, hashVal2); byte[] hashVal1 = msgDigest.digest(input); clearArray(passArrays.fill(hashVal1, (byte) 0); Arrays.fill(hashVal2, (byte) 0); clearArray(input) return arraysEqual; } // Load the hash value stored in password.bin /* Encrypts password & salt and zeroes both */ private byte[] encryptPassword(char[] pass, byte[] hashVal2 = loadBytes("password.bin"); salt) throws GeneralSecurityException { booleanKeySpec arraysEqualspec = new PBEKeySpec(pass, salt, 65536, 128); Arrays.equalsfill(hashVal1pass, hashVal2(char) 0); Arrays.fill(salt, clearArray(hashVal1byte) 0); SecretKeyFactory f = clearArray(hashVal2SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); return arraysEqualf.generateSecret(spec).getEncoded(); } /** private* Indicates if both byte[] generateSalt(int n) { // Generate a random byte array of length n } private byte[] appendArrays(byte[] a, byte[] barrays are equal * but uses same amount of time if they are the same or different * to prevent timing attacks */ public static boolean timingEquals(byte b1[], byte b2[]) { // Return a new array of a[] appended to b[] } private void clearArray(byte[] a) {boolean result = true; int len = b1.length; if (len > b2.length) { result = false; len = b2.length; } for (int i = 0; i < a.lengthlen; i++) { result a&= (b1[i] == 0; } b2[i]); } return result; } private void saveBytes(byte[] bytes, String filename) throws IOException { // ... write bytes to the file } private byte[] loadBytes(String filename) throws IOException { // ... read bytes to the file } } |
In both the setPassword()
and checkPassword()
methods, the cleartext representation of the password is erased immediately after it is converted into a hash value. Consequently, attackers must work harder to retrieve the cleartext password after the erasure. Providing guaranteed erasure is extremely challenging, is likely to be platform specific, and may even be impossible because of copying garbage collectors, dynamic paging, and other platform features that operate below the level of the Java language.
Furthermore, we use a timingEquals()
method to validate the password. While doing a simple byte comparison, it takes the same time for both successful matches and unsuccessful ones; consequently thwarting timing attacks.
Finally, it uses PBKDF2 which, unlike MessageDigest
, is specifically designed for hashing passwords.
Applicability
Passwords stored without a secure hash are exposed to malicious users. Violations of this guideline generally have a clear exploit associated with them.
...