SV.HASH.NO_SALT
Use of a one-way cryptographic hash without a salt
This error is reported when a one-way cryptographic hash function is applied to a single piece of input data without salt being added.
Vulnerability and risk
If software stores one-way cryptographic hashes calculated for user passwords, appending or prepending a unique salt to each password is recommended; so if two users have the same password, the hashes will be different.
If an attacker gains access to the hashes, the lack of a salt makes it easier to guess multiple user passwords.
Vulnerable code example
/*
* Adds a new user; stores login and password hash.
*/
public void addUser(String login, byte[] password) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(password); // < -- calculate hash for the password
byte[] hash = md.digest();
storeHashString(login, Hex.encodeHexString(hash));
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
}
/*
* Validates user login information. Returns true if user exists, and
entered password hash matches stored password hash;
* otherwise returns false.
*/
public boolean login(String login, byte[] password) {
try {
String storedHash = readHashString(login);
if (storedHash != null) {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(password); // < -- calculate hash for the password
byte[] hash = md.digest();
return MessageDigest.isEqual(hash, Hex.decodeHex(storedHash.toCharArray()));
} else {
return false;
}
} catch (NoSuchAlgorithmException | DecoderException e) {
throw new IllegalStateException(e);
}
}
. . .
hashManager.addUser("Alice", "changeit".getBytes());
hashManager.addUser("Bob", "changeit".getBytes());
In this example, both users – “Alice” and “Bob” have password “changeit”; and both stored hashes are “b91cd1a54781790beaa2baf741fa6789”. Then, if an attacker steals the password hashes table, he can easily reverse “b91cd1a54781790beaa2baf741fa6789” to “changeit” using a dictionary.
Fixed code example
/*
* Adds a new user; stores login and "salt:hash".
*/
public void addUser(String login, byte[] password) {
try {
byte[] salt = new byte[16];
secureRandom.nextBytes(salt);
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(salt); // use random salt
md.update(password); // and password
byte[] hash = md.digest();
storeHashString(login, Hex.encodeHexString(salt) + ":" + Hex.encodeHexString(hash));
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
}
/*
* Validates user login information. Returns true if user exists, and
entered password matches stored "salt:hash"; otherwise returns false.
*/
public boolean login(String login, byte[] password) {
try {
String storedSaltAndHash = readHashString(login);
if (storedSaltAndHash != null) {
String[] saltAndHash = storedSaltAndHash.split(":");
if (saltAndHash.length != 2) {
throw new IllegalStateException("Expected salt:hash string");
}
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(Hex.decodeHex(saltAndHash[0].toCharArray())); // use stored salt
md.update(password);
byte[] hash = md.digest();
return MessageDigest.isEqual(hash, Hex.decodeHex(saltAndHash[1].toCharArray()));
} else {
return false;
}
} catch (NoSuchAlgorithmException | DecoderException e) {
throw new IllegalStateException(e);
}
}
. . .
hashManager.addUser("Alice", "changeit".getBytes());
hashManager.addUser("Bob", "changeit".getBytes());
Alice -> 03c016ca60ee8c53aa8f24301a08ec27:de88b6fc874d83eb30aa9020f431bde3 Bob -> e2eafef8be75cd58b7ed598ddb37e128:4bc3b11a2bf5854b66543f8ba9ffa2c6
Related checkers
External guidance
Security training
Application security training materials provided by Secure Code Warrior.