The improper storage of passwords poses a significant security risk to software applications. This vulnerability arises when passwords are stored in plaintext or with a fast hashing algorithm. To exploit this vulnerability, an attacker typically requires access to the stored passwords.

Why is this an issue?

Attackers who would get access to the stored passwords could reuse them without further attacks or with little additional effort.
Obtaining the plaintext passwords, they could then gain unauthorized access to user accounts, potentially leading to various malicious activities.

What is the potential impact?

Plaintext or weakly hashed password storage poses a significant security risk to software applications.

Unauthorized Access

When passwords are stored in plaintext or with weak hashing algorithms, an attacker who gains access to the password database can easily retrieve and use the passwords to gain unauthorized access to user accounts. This can lead to various malicious activities, such as unauthorized data access, identity theft, or even financial fraud.

Credential Reuse

Many users tend to reuse passwords across multiple platforms. If an attacker obtains plaintext or weakly hashed passwords, they can potentially use these credentials to gain unauthorized access to other accounts held by the same user. This can have far-reaching consequences, as sensitive personal information or critical systems may be compromised.

Regulatory Compliance

Many industries and jurisdictions have specific regulations and standards to protect user data and ensure its confidentiality. Storing passwords in plaintext or with weak hashing algorithms can lead to non-compliance with these regulations, potentially resulting in legal consequences, financial penalties, and damage to the reputation of the software application and its developers.

How to fix it in Java Cryptography Extension

Code examples

Noncompliant code example

The derived key is vulnerable because the cost factor (rounds) is too low for the chosen algorithm.

private fun deriveKey(password: String, salt: ByteArray): SecretKey? {
  val keySpec = PBEKeySpec(password.toCharArray(), salt, 120000, 256) // Noncompliant
  val secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA512")
  return secretKeyFactory.generateSecret(keySpec)
}

Compliant solution

private fun deriveKey(password: String, salt: ByteArray): SecretKey? {
  val keySpec = PBEKeySpec(password.toCharArray(), salt, 210000, 256)
  val secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA512")
  return secretKeyFactory.generateSecret(keySpec)
}

How does this work?

Select the correct PBKDF2 parameters

If PBKDF2 must be used, be aware that default values might not be considered secure.
Depending on the algorithm used, the number of iterations should be adjusted to ensure that the derived key is secure. The following are the recommended number of iterations for PBKDF2:

Note that PBKDF2-HMAC-SHA256 is recommended by NIST.
Iterations are also called "rounds" depending on the library used.

When recommended cost factors are too high in the context of the application or if the performance cost is unacceptable, a cost factor reduction might be considered. In that case, it should not be chosen under 100,000.

Going the extra mile

Pepper

In a defense-in-depth security approach, peppering can also be used. This is a security technique where an external secret value is added to a password before it is hashed.
This makes it more difficult for an attacker to crack the hashed passwords, as they would need to know the secret value to generate the correct hash.
Learn more here.

Resources

Documentation

Standards