Any time an application stores a password as cleartext (unencrypted text data), its value is potentially exposed in a variety of ways. To prevent this information from being inadvertently leaked, this exposure must be limited. While a program will receive the password from the user as cleartext, this should be the last time it is in this form.
Hash functions allow programs to indirectly compare an input password to the original, without storing a cleartext or decryptable version of the password. This approach minimizes the exposure of the password without presenting any practical disadvantages.
Cryptographic Hash Functions
The value that a hash function outputs is called the hash value. Another term for hash value is message digest. Hash functions are computationally feasible functions whose inverses are computationally infeasible. This means that in practice, one can encode a password to a hash value, while they are also unable to decode it. The equality of the passwords can be tested through the equality of their hash values.
It is important that you append a salt to the password you are hashing. A salt is a randomly generated piece of data that is stored along with the hash value. The use of a salt helps prevents brute-force attacks against the hash value, provided the salt is long enough. 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 if their passwords are the same.
The choice of hash function and salt length presents a trade-off between security and performance. If it takes longer to compute a hash value, then the performance of a brute-force attack will be lowered. This will come at the cost of slowing down the program when it validates passwords. If a longer salt length is used, then the performance of a brute-force attack will be lowered at the cost of the extra storage space required.
Java's MessageDigest
class provides the functionality of various cryptographic hash functions. Be careful not to pick a defective function such as MD-5. Publications are often available when a hash function is proven defective. Hash functions such as SHA-1 and SHA-2 are maintained by the NSA, and are currently considered safe.
Noncompliant Code Example
This noncompliant code example encrypts and decrypts the password stored in credentials.pw.
Code Block | ||
---|---|---|
| ||
public final class Password { private void setPassword(byte[] pass) throws Exception { Â bytes[] encrypted = encrypt(pass); //arbitrary encryption scheme clearArray(pass); Â Â saveBytes(encrypted,"password.bin"); //encrypted password to password.bin } private boolean checkPassword(byte[] pass) throws Exception { boolean arrays_equal; byte[] encrypted = loadBytes("password.bin"); //load the encrypted password byte[] decrypted = decrypt(encrypted); arrays_equal = Arrays.equal(decrypted, pass); clearArray(decrypted); clearArray(pass); return arrays_equal; } private clearArray(byte[] a) { //set all of the elements in a to zero } } |
An attacker could potentially decrypt this file to discover the password. This attacker could be someone knows or has figured out the encryption scheme being used by the program.
Noncompliant Code Example
This noncompliant code examples implements the SHA-1
hash function through the MessageDigest
class to compare hash values instead of cleartext strings.
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 sha_1 = MessageDigest.getInstance("SHA-1"); byte[] hashVal = sha_1.digest((pass+salt).getBytes()); //encode the string and salt saveBytes(salt, "salt.bin"); saveBytes(hashVal,"password.bin"); //save the hash value to credentials.bin } private boolean checkPassword(String pass) throws Exception { byte[] salt = loadBytes("salt.bin"); MessageDigest sha_1 = MessageDigest.getInstance("SHA-1"); byte[] hashVal1 = sha_1.digest((pass+salt).getBytes()); //encode the string and salt byte[] hashVal2 = loadBytes("password.bin"); //load the hash value stored in password.bin return Arrays.equals(hashVal1, hashVal2); } private byte[] generateSalt(int n) { //Generate a random byte array of length n } } |
Even if an attacker knows that the program stores passwords using SHA-1
and a 12-byte salt, they will be unable to get the value of the password from password.bin
and salt.bin
.
While this fixes the decryption problem from the previous noncompliant code example, at runtime this code may inadvertently store the passwords as cleartext. Java string objects are immutable, so any time you assign a new value to them it does not necessarily overwrite the part of memory where the string was previously stored. This means that it is very difficult to ensure that the cleartext is actually cleared from memory. See MSC10-J. Limit the lifetime of sensitive data for more information.
Compliant Solution
This compliant solution addresses the problems from the previous noncompliant examples.
Code Block | ||
---|---|---|
| ||
import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public final class Password { private void setPassword(byte[] pass) throws Exception { byte[] salt = generateSalt(12); byte[] input = appendArrays(pass, salt); MessageDigest sha_1 = MessageDigest.getInstance("SHA-1"); byte[] hashVal = sha_1.digest(input); //encode the string and salt  clearArray(pass);  clearArray(input); saveBytes(salt, "salt.bin");  saveBytes(hashVal,"password.bin"); //save the hash value to credentials.pw } private boolean checkPassword(byte[] pass) throws Exception { byte[] salt = loadBytes("salt.bin"); byte[] input = appendArrays(pass, salt); MessageDigest sha_1 = MessageDigest.getInstance("SHA-1"); byte[] hashVal1 = sha_1.digest(input); //encode the string and salt clearArray(pass); clearArray(input); byte[] hashVal2 = loadBytes("credentials.pw"); //load the hash value stored in credentials.pw return Arrays.equals(hashVal1, hashVal2); } private byte[] generateSalt(int n) { //Generate a random byte array of length n } private byte[] appendArrays(byte[] a, byte[] b) { //Return a new array of a appended to b } private void clearArray(byte[] a) { //set all of the elements in a to zero } } |
In both the setPassword()
and checkPassword()
methods, the cleartext representation of the password is erased as soon as it is converted into a hash value. After this happens, there is no way for an attacker to get the password as cleartext.
Exceptions
MSC18-EX0 Applications such as password managers may need to retrieve the original password in order to enter it into a third-party application. This is okay even though it violates the guideline. The difference here is that the password manager is accessed by a single user. The program will always have the user's permission to store their passwords in this way. Therefore, provided the user is competent, the program's operation will be safe.
Risk Assessment
Violations of this rule have to be manually detected because it is a consequence of the overall design of the password storing mechanism. It is pretty unlikely, since it will occur around once or twice in a program that uses passwords. As demonstrated above, almost all violations of this rule have a clear exploit associated with them.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
MSC18-J | medium | likely | high | P6 | L2 |
Bibliography
Wiki Markup |
---|
\[[API 2006|AA. Bibliography#API 06]\] Class {{java.security.MessageDigest}} |
Wiki Markup |
---|
\[[API 2006|AA. Bibliography#API 06]\] Class {{java.lang.String}} |
http://www.javapractices.com/topic/TopicAction.do?Id=216 Passwords never in clear text
http://en.wikipedia.org/wiki/Salt_(cryptography) Salt (cryptography)
http://en.wikipedia.org/wiki/Cryptographic_hash_function Cryptographic hash function