Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: edits to text and NCCE's for consistency.

...

A good practice is to always use a salt in addition to the password being hashed. A salt is a unique randomly generated piece of data that is stored and used to generate the hash value along with the password. along with the hash value.  Each password should have its own salt associated with it. If  If a single salt were used for more than one password an attacker could determine when a user has a commonly used password. Password specific salts are usually stored along with their corresponding hash values. System specific salts that are stored separately from the hash values may also be used to increase the difficulty of deriving passwords if a malicious actor obtains a copy of the hash values and salts.

The choice of hash function and salt length presents a trade-off between security and performance. Increasing the effort required for effective brute-force attacks by choosing a stronger hash function can also increase the time required to validate a password.  As time passes best practices around password management evolve to keep inverse hashing password derivation computationally infeasible.  The documents NIST 800-63 and OWASP ASVS are good places to consult for the current best practices around cryptographic hashing.

Java's javax.crypto package provides implementations of various cryptographic hash functions. Avoid defective functions such that have known weaknesses, such as the Message-Digest Algorithm (MD5). 

...

Code Block
bgColor#FFcccc
public final class Password {
  private void setPassword(byte[] pass) throws Exception {
    // Arbitrary encryption scheme
    bytesbyte[] encrypted = encrypt(pass);
    clearArray(pass);    
    // Encrypted password to password.bin
    saveBytes(encrypted,"password.bin");
    clearArray(encrypted); 
  }

  boolean checkPassword(byte[] pass) throws Exception {
    // Load the encrypted password
    byte[] encrypted = loadBytes("password.bin"); 
    byte[] decrypted = decrypt(encrypted);
    clearArray(encrypted);
    boolean arraysEqual = Arrays.equal(decrypted, pass);
    clearArray(encrypted);
    clearArray(decrypted);
    clearArray(pass);
    return arraysEqual;
  }

  private void clearArray(byte[] a) {
    for (int i = 0; i < a.length; i++) {
      a[i] = 0;
    }
  }


  private byte[] encrypt(byte[] clearValue) {
    // ... symmetric encryption of clearValue bytes, returning the encrypted value
  }


  private byte[] decrypt(byte[] encryptedValue) {
    // ... symmetric decryption of  encryptedValue bytes, returning clear value
  }


  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 
  }
}

This is a very simple password mechanism that only stores one password for the system.  The flaw in this approach is that the stored password is encrypted in a way that it can be decrypted to compare it to the user's password input. An attacker could potentially decrypt the password file to discover the password, particularly when the attacker has knowledge of the key and encryption scheme used by the program. Passwords should be protected even from system administrators and privileged users. Consequently, using encryption is only partly effective in mitigating password disclosure threats.

...

Code Block
bgColor#FFcccc
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public final class Password {
  private SecureRandom random = new SecureRandom();

  private void setPassword(String pass) throws Exception {
    byte[] salt = new byte[12];
    random.nextBytes(salt);
    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 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 
  }
}

This is a very simple password mechanism that only stores one password and one salt for the system. Even when an attacker knows that the program stores passwords using SHA-256 and a 12-byte salt, he or she will be unable to retrieve the actual password from password.bin and salt.bin.

...

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

...