Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

This compliant solution addresses the problems from the previous noncompliant code example by using a byte array to store the password:

Code Block
bgColor#ccccff
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
  
final class Password {
  private SecureRandom random = new SecureRandom();
  private final int SALT_BYTE_LENGTH = 12;
  private final int ITERATIONS = 100000;
  private final int KEY_BIT_LENGTH = 128;
  private final String ALGORITHM = "PBKDF2WithHmacSHA256";
    
  /* Set password to new value, zeroing out password */
  void setPassword(char[] pass)
      throws IOException, GeneralSecurityException  {
    byte[] salt = new byte[SALT_BYTE_LENGTH];
    random.nextBytes(salt);
    saveBytes(salt, "salt.bin");    
    byte[] hashVal = hashPassword( pass, salt); 
    saveBytes(hashVal,"password.bin");
    Arrays.fill(hashVal, (byte) 0);
  }

  /* Indicates if given password is correct */
  boolean checkPassword(char[] pass)
      throws IOException, GeneralSecurityException  {
    byte[] salt = loadBytes("salt.bin");
    byte[] hashVal1 = hashPassword( pass, salt);
    // Load the hash value stored in password.bin
    byte[] hashVal2 = loadBytes("password.bin");
    boolean arraysEqual = timingEquals( hashVal1, hashVal2);
    Arrays.fill(hashVal1, (byte) 0);
    Arrays.fill(hashVal2, (byte) 0);
    return arraysEqual;
  }
  
  /* Encrypts password & salt and zeroes both */
  private byte[] hashPassword(char[] pass, byte[] salt)
      throws GeneralSecurityException {
    KeySpec spec = new PBEKeySpec(pass, salt, ITERATIONS, KEY_BIT_LENGTH);
    Arrays.fill(pass, (char) 0);
    Arrays.fill(salt, (byte) 0);
    SecretKeyFactory f = SecretKeyFactory.getInstance(ALGORITHM);
    return f.generateSecret(spec).getEncoded();
  }

  /**
   * Indicates if both byte arrays 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[]) {
    boolean result = true;
    int len = b1.length;
    if (len != b2.length) {
      result = false;
    }
    if (len > b2.length) {
      len = b2.length;
    }
    for (int i = 0; i < len; i++) {
      result &= (b1[i] == 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
  }
}

First, this compliant solution uses byte array to store the password.

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.

...