...
A good practice is to always append use a salt in addition to the password being hashed. A salt is a unique (often sequential) or randomly generated piece of data that is stored along with the hash value. The use of a salt helps prevent brute-force attacks against the hash value, provided that the salt is long enough to generate sufficient entropy (shorter salt values cannot significantly slow down a brute-force attack). Each password should have its own salt associated with it. If a single salt were used for more than one password, two users would be able to see whether their passwords are the samepassword an attacker could determine when a user has a commonly used password.
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. Increasing the length of the salt makes brute-force attacks more difficult but requires additional storage space As time passes additional best practices around password management evolve to keep password hashing computationally infeasible. The documents NIST 800-63 and OWASP ASVS are good places to consult for the current best practices around password management.
Java's MessageDigest
class javax.crypto
package provides implementations of various cryptographic hash functions. Avoid defective functions such as the Message-Digest Algorithm (MD5). Hash functions such as Secure Hash Algorithm (SHA)-1 and SHA-2 are maintained by the National Security Agency and are currently considered safe. In practice, many applications use SHA-256 because this hash function has reasonable performance while still being considered secure.
Noncompliant Code Example
...
Code Block | ||
---|---|---|
| ||
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 } } |
...
The parametric values (SALT_BYTE_LENGTH, ITERATIONS, KEY_BIT_LENGTH, ALGORITHM) should be set to values that reflect current best practices. It should also be noted that once these parametric values are set they can not be changed without having to re-hash all passwords with the new parametric values.
...
Applications such as password managers may need to retrieve the original password to enter it into a third-party application. This is permitted even though it violates this guideline. The password manager is accessed by a single user and always has the user's permission to store his or her passwords and to display those passwords on command. Consequently, the limiting factor to safety and security is the user's competence rather than the program's operation.
Bibliography
[API 2013] | Class String MessageDigest |
[Hirondelle 2013] | Passwords Never Clear in Text |
[OWASP 2012] | "Why Add Salt?" |
[Paar 2010] | Chapter 11, "Hash Functions" |
OWASP ASVS | |
[NIST 2017] | NIST 800-63 |
...