...
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 and used to generate 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 along with the password. 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. In addition to password, two users would be able to see whether their passwords are the same-unique salts, system-unique 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. Increasing the length of the salt makes brute-force attacks more difficult but requires additional storage space As time passes best practices around password management evolve to keep 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 MessageDigest
class 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). 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 | ||
---|---|---|
| ||
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(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 this 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.
...
This noncompliant code example uses the SHA-256
hash function through the MessageDigest
class to compare hash values instead of cleartext strings, but . It uses SecureRandom
to generate a strong salt, as recommended by MSC02-J. Generate strong random numbers. However, it uses a String
to store the password:
Code Block | ||
---|---|---|
| ||
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 = generateSalt(12 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[] generateSalt(int n)bytes, String filename) throws IOException { // Generate a random byte array of length n... 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
.
...
This compliant solution addresses the problems from the previous noncompliant code example by using a byte
array to store the passwordexamples:
Code Block | ||
---|---|---|
| ||
import java.security.MessageDigestGeneralSecurityException; import java.security.SecureRandom; import java.security.NoSuchAlgorithmExceptionspec.KeySpec; public import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; final class Password { private void setPassword(byte[] pass) throws Exception {SecureRandom random = new SecureRandom(); private final byte[] saltint SALT_BYTE_LENGTH = generateSalt(12); private final byte[]int inputITERATIONS = appendArrays(pass, salt)100000; private final MessageDigestString msgDigestALGORITHM = MessageDigest.getInstance("SHA-256")"PBKDF2WithHmacSHA256"; // Encode the string and salt* Set password to new value, zeroing out password */ void bytesetPassword(char[] hashVal = msgDigest.digest(input); pass) throws IOException, GeneralSecurityException { clearArray(pass); byte[] salt = new byte[SALT_BYTE_LENGTH]; clearArrayrandom.nextBytes(inputsalt); saveBytes(salt, "salt.bin"); // Save the hash value to password.binbyte[] hashVal = hashPassword(pass, salt); saveBytes(hashVal,"password.bin"); clearArray(salt); clearArray(hashVal); } Arrays.fill(hashVal, (byte) 0); } /* Indicates if given password is correct */ boolean checkPassword(bytechar[] pass) throws ExceptionIOException, GeneralSecurityException { byte[] salt = loadBytes("salt.bin"); byte[] inputhashVal1 = appendArrayshashPassword(pass, salt); // MessageDigest msgDigestLoad the hash value stored in password.bin byte[] hashVal2 = MessageDigest.getInstanceloadBytes("SHA-256password.bin"); //boolean EncodearraysEqual the string and salt= timingEquals(hashVal1, hashVal2); byte[] hashVal1 = msgDigest.digest(input); clearArray(pass); clearArray(input); // Load the hash value stored in password.bin byte[] hashVal2 = loadBytes("password.bin"); boolean arraysEqual = Arrays.equals(hashVal1, hashVal2); clearArray(hashVal1); clearArray(hashVal2Arrays.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 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; } if (len > b2.length) { len = b2.length; } for (int i = 0; i < a.lengthlen; i++) { aresult &= (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 } } |
This is a very simple password mechanism that only stores one password and one salt for the system.
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.
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.
The parametric values (SALT_BYTE_LENGTH, ITERATIONS, 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.
Applicability
Passwords stored without a secure hash are exposed to malicious users. Violations of this guideline generally have a clear exploit associated with them.
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.
Automated Detection
Tool | Version | Checker | Description | ||||||
---|---|---|---|---|---|---|---|---|---|
Parasoft Jtest |
| CERT.MSC62.PCCF CERT.MSC62.PWDPROP CERT.MSC62.PWDXML CERT.MSC62.WCPWD CERT.MSC62.WPWD CERT.MSC62.PLAIN CERT.MSC62.PTPT CERT.MSC62.UTAX | Avoid storing usernames and passwords in plain text in Castor 'jdo-conf.xml' files Ensure that passwords are not stored as plaintext and are sufficiently long Ensure that passwords are not stored as plaintext and are sufficiently long Avoid unencrypted passwords in WebSphere 'ibm-webservicesclient-ext.xmi' files Avoid unencrypted passwords in WebSphere 'ibm-webservices-ext.xmi' files Password information should not be included in properties file in plaintext Avoid using plain text passwords in Axis 'wsdd' files Avoid using plain text passwords in Axis2 configuration files |
Bibliography
Class
String
[API 2013] |
MessageDigest
[Hirondelle 2013] | Passwords Never Clear in Text |
[OWASP 2012] | "Why Add Salt?" |
[Paar 2010] | Chapter 11, "Hash Functions" |
...
...