Cleartext refers to text data that is not encrypted.
Any time an application stores a password as cleartext, its value is potentially exposed Programs that store passwords as cleartext (unencrypted text data) risk exposure of those passwords in a variety of ways. Obviously this exposure must be limited. While a program will receive the password from the user Although programs generally receive passwords from users as cleartext, they should ensure that the passwords are not stored as cleartext.
An acceptable technique for limiting the exposure of passwords is the use of hash functions, which this should be the last time it is in this form. Hash functions allow programs to indirectly compare an input password to the original , password string without storing a cleartext or decryptable version of the password. This approach will therefore minimize minimizes the exposure of the password without presenting any practical disadvantages.
Cryptographic Hash Functions
The value that produced by a hash function outputs is called the hash value. Another term for hash value is or message digest. Hash Hash functions are computationally feasible functions whose inverses are computationally infeasible. This means that in In practice, one can encode a password can be encoded to a hash value quickly, while they are also unable to decode it, but decoding remains infeasible. The equality of the passwords can be tested through the equality of their hash values.
Java's MessageDigest class provides the functionality of various cryptographic hash functions. Be careful not to use any defective hash functions, such as MD5.
It is also important that you append a salt to the password you are hashingA 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 randomly generated during the creation of the program (and consistent throughout the rest of its implementation). The use of a salt helps prevents dictionary attacks against the hash value, provided the salt is long enough.
Noncompliant Code Example
stored and used to generate the hash value along with the password. Each password should have its own salt associated with it. 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-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. 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 javax.crypto
package provides implementations of various cryptographic hash functions. Avoid functions that have known weaknesses, such as the Message-Digest Algorithm (MD5).
Noncompliant Code Example
This noncompliant code example encrypts and decrypts the password stored in password.bin
using a symmetric key algorithm:
Code Block | ||
---|---|---|
| ||
public final | ||
Code Block | ||
| ||
class Password { public staticprivate void mainsetPassword(Stringbyte[] argspass) throws IOExceptionException { // Arbitrary encryption scheme charbyte[] passwordencrypted = new char[100]; encrypt(pass); clearArray(pass); BufferedReader br = new BufferedReader(new InputStreamReader( // Encrypted password to password.bin new FileInputStreamsaveBytes(encrypted,"credentialspassword.txtbin")); clearArray(encrypted); } boolean checkPassword(byte[] pass) throws Exception { // ReadsLoad the encrypted password byte[] encrypted = loadBytes("password into the char array, returns the number of bytes read.bin"); byte[] decrypted = decrypt(encrypted); clearArray(encrypted); intboolean narraysEqual = br.read(password)Arrays.equal(decrypted, pass); clearArray(decrypted); clearArray(pass); return arraysEqual; } //private Decrypt password, perform operationsvoid clearArray(byte[] a) { for (int i = n - 10; i < a.length; i >++) { a[i] = 0; i-- } } private byte[] encrypt(byte[] clearValue) { // Manually clear out the password immediately after use password[i] = 0; } br.close(); ... 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 code is incorrect because it decrypts the password stored in credentials.txt
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 find out what discover the password is. This attacker could be someone knows or has figured out the encryption scheme being , 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.
Noncompliant Code Example
This noncompliant code example uses the SHA-256
hash function through the MessageDigest
class to compare hash values instead of cleartext strings. 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 HashExamplesPassword { private StringSecureRandom saltrandom = "ia0942980234241sadfaewvo32"; //Randomly generated new SecureRandom(); private void setPassword(String pass) throws Exception { MessageDigest sha_1byte[] salt = new byte[12]; random.nextBytes(salt); MessageDigest msgDigest = MessageDigest.getInstance("SHA-1256"); // Encode the string and salt byte[] hashVal = sha_1msgDigest.digest((pass+salt).getBytes()); //encode the string and salt saveBytes(hashValsalt, "credentialssalt.pwbin"); //save Save the hash value to credentials.pwpassword.bin saveBytes(hashVal,"password.bin"); } private boolean checkPassword(String pass) throws Exception { byte[] salt = loadBytes("salt.bin"); MessageDigest sha_1msgDigest = MessageDigest.getInstance("SHA-1256"); // Encode the string and salt byte[] hashVal1 = sha_1msgDigest.digest((pass+salt).getBytes()); //encode Load the hash value stringstored andin saltpassword.bin byte[] hashVal2 = loadBytes("credentialspassword.pwbin"); //load the hash value stored in credentials.pwreturn 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 { return// Arrays.equals(hashVal1, hashVal2);... 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
.
Although this approach solves the decryption problem from the previous noncompliant code example, this program may inadvertently store the passwords as cleartext in memory. Java String
objects are immutable and can be copied and internally stored by the Java Virtual Machine. Consequently, Java lacks a mechanism to securely erase a password once it has been stored in a String
. See MSC59 code examples implements the SHA-1
hash function through the MessageDigest
class in order to compare hash values instead of cleartext strings. While this fixes the above decryption problem, at runtime this code may inadvertently store the passwords as cleartext. This is due to the fact that the pass
arguments may not be cleared from memory by the Java garbage collector. See "MSC10-J. Limit the lifetime of sensitive data", for more information.
Compliant Solution
This compliant solution addresses the problems from the previous noncompliant code examples:
Code Block | ||
---|---|---|
| ||
import java.security.GeneralSecurityException; import java.security.MessageDigestSecureRandom; import java.security.NoSuchAlgorithmExceptionspec.KeySpec; public import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; final class HashExamplesPassword { private byte[]SecureRandom saltrandom = "ia0942980234241sadfaewvo32".getBytes new SecureRandom(); //Randomly generated 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(bytechar[] pass) throws IOException, ExceptionGeneralSecurityException { byte[] inputsalt = appendArrays(pass, salt)new byte[SALT_BYTE_LENGTH]; MessageDigest sha_1 = MessageDigest.getInstance("SHA-1"); random.nextBytes(salt); saveBytes(salt, "salt.bin"); byte[] hashVal = sha_1.digest(input); //encode the string and salt  clearArray(pass);  clearArray(input);  saveBytes(hashVal,"credentials.pw"); //save the hash value to credentials.pw } private boolean checkPassword(byte[] pass) throws ExceptionhashPassword(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 { byte[]KeySpec inputspec = new appendArraysPBEKeySpec(pass, salt, ITERATIONS); MessageDigest sha_1 = MessageDigest.getInstance("SHA-1"Arrays.fill(pass, (char) 0); Arrays.fill(salt, (byte) 0); byte[]SecretKeyFactory hashVal1f = 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)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 privatevoid appendArrayssaveBytes(byte[] abytes, byte[]String bfilename) throws IOException { //Return a... newwrite arraybytes ofto a appended to bthe file } private clearArray(byte[] loadBytes(String afilename) throws IOException { //set all... ofread thebytes elementsto in a to zerothe 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 solution fixes the vulnerabilities in the previous noncompliant examples. In both setPassword
and checkPassword
, the cleartext representation of the password is erased as soon as immediately after it is converted into a hash value. After this happens, there is no way for an attacker to get the password as cleartext.
Exceptions
. 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
[API 2013] | |
[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 |
...
There are a few cases where you may be forced to encrypt passwords or store them as cleartext. These cases will happen when you are extending code or an application that you cannot change. For example, a password manager may need to input passwords into other programs as cleartext. Another example is if you are working with a library that gives you the password as a Java string object, causing the same vulnerability as in the second noncompliant example. In these cases your best strategy may be to use slightly vulnerable methods such as encryption, unless you can change the other code.