Secure Password Hashing in Java: Best Practices and Code Examples

In the domain of digital security, password hashing stands as a critical line of defense against unauthorized access. However, the landscape of hashing algorithms has evolved significantly, with some methods becoming obsolete and newer, more secure techniques emerging. This article delves into why traditional methods like SHA-512 are no longer sufficient, the importance of salting and slowing down hashing processes, and provides practical Java code examples for modern password hashing techniques.

The Inadequacy of SHA-512 for Password Hashing

SHA-512, part of the SHA-2 family, is a cryptographic hash function that was once a standard for securing passwords. However, it's now considered inadequate for password hashing due to:

  1. Speed: SHA-512 is designed to be fast. Unfortunately, this makes it vulnerable to brute-force attacks, where attackers can quickly try millions of password combinations.
  2. Lack of Salting: While SHA-512 itself doesn’t incorporate salting, it’s often implemented without it, making it susceptible to rainbow table attacks.

The Crucial Role of Salting

Salting involves adding a random string to each password before hashing. This practice thwarts rainbow table attacks, where precomputed hash tables are used for cracking passwords. By ensuring that each password hash is unique, salting effectively neutralizes this threat.

Slowing Down Hashing Processes

Modern password hashing algorithms intentionally slow down the hashing process to deter attacks. This approach makes brute-force attacks impractical by increasing the computational and time resources required to crack each password. Here's how they achieve this:

1. Computationally Intensive Hashing

2. Memory Intensive Operations

3. Built-in Salting

Effectiveness Against Different Types of Attacks

Real-World Impact

Modern Password Hashing Techniques

1. BCrypt

BCrypt is a widely used hashing algorithm that automatically handles salting and is intentionally slow to hinder brute-force attacks.

Example:

Java
 
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

public class BCryptHashing {
    public static String hashPassword(String password) {
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        return passwordEncoder.encode(password);
    }
}


2. Argon2

Argon2, the winner of the 2023 Password Hashing Competition, offers customizable resistance against GPU and memory-based attacks.

Example:

Java
 
import org.bouncycastle.crypto.generators.Argon2BytesGenerator;
import org.bouncycastle.crypto.params.Argon2Parameters;

public class Argon2Hashing {
    public static String hashPassword(String password) {
      
        // Set realistic values for Argon2 parameters
        int parallelism = 2; // Use 2 threads
        int memory = 65536; // Use 64 MB of memory
        int iterations = 3; // Run 3 iterations
        int hashLength = 32; // Generate a 32 byte (256 bit) hash
      
        Argon2BytesGenerator generator = new Argon2BytesGenerator();
        Argon2Parameters.Builder builder = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_id)
                .withSalt(salt) // You need to generate a salt
                .withParallelism(parallelism) // Parallelism factor
                .withMemoryAsKB(memory) // Memory cost
                .withIterations(iterations); // Number of iterations

        generator.init(builder.build());
        byte[] result = new byte[hashLength];
        generator.generateBytes(password.toCharArray(), result);
        return Base64.getEncoder().encodeToString(result);
    }
}


3. PBKDF2

PBKDF2 (Password-Based Key Derivation Function 2) is part of the RSA Laboratories' PKCS series and is designed to be computationally intensive, offering adjustable iterations for enhanced security.

Example:

Java
 
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import java.util.Base64;

public class PBKDF2Hashing {
    public static String hashPassword(String password) throws Exception {
        SecureRandom random = new SecureRandom();
        byte[] salt = new byte[16];
        random.nextBytes(salt);

        KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 256);
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");

        byte[] hash = factory.generateSecret(spec).getEncoded();
        return Base64.getEncoder().encodeToString(hash);
    }
}


4. SHA-512 with Salt (Not Recommended)

Despite its vulnerabilities, understanding SHA-512 can be educational.

Example:

Java
 
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Base64;

public class SHA512Hashing {
    public static String hashWithSalt(String password) throws Exception {
        SecureRandom random = new SecureRandom();
        byte[] salt = new byte[16];
        random.nextBytes(salt);

        MessageDigest md = MessageDigest.getInstance("SHA-512");
        md.update(salt);

        byte[] hashedPassword = md.digest(password.getBytes());
        return Base64.getEncoder().encodeToString(hashedPassword);
    }
}


Hashed Input Password Verification

To verify a password using any hashing algorithm, the typical approach is to hash the input password using the same algorithm and parameters (like salt, iteration count, etc.) that were used when the original password hash was created. Then, you compare the newly generated hash with the stored hash. However, with algorithms like BCrypt, Argon2, and PBKDF2, the comparison is often simplified using built-in functions that handle these steps for you.

Let's go through each algorithm with Java code snippets for verifying a password:

1. Verifying Password with BCrypt

BCrypt has a built-in method for verifying passwords.

Example:

Java
 
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

public class BCryptHashing {
    public static boolean verifyPassword(String inputPassword, String storedHash) {
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        return encoder.matches(inputPassword, storedHash);
    }
}


2. Verifying Password with Argon2 (Using Bouncy Castle)

For Argon2, you will need to store the salt and other parameters used to hash the password originally. Then, use these to hash the input password and compare it with the stored hash.

Example:

Java
 
import org.bouncycastle.crypto.generators.Argon2BytesGenerator;
import org.bouncycastle.crypto.params.Argon2Parameters;
import java.util.Base64;

public class Argon2Hashing {
    public static boolean verifyPassword(String inputPassword, String storedHash, byte[] salt, int parallelism, int memory, int iterations, int hashLength) {
        Argon2BytesGenerator generator = new Argon2BytesGenerator();
        Argon2Parameters.Builder builder = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_id)
                .withSalt(salt)
                .withParallelism(parallelism)
                .withMemoryAsKB(memory)
                .withIterations(iterations);

        generator.init(builder.build());
        byte[] result = new byte[hashLength];
        generator.generateBytes(inputPassword.toCharArray(), result);
        String newHash = Base64.getEncoder().encodeToString(result);

        return newHash.equals(storedHash);
    }
}


3. Verifying Password with PBKDF2

Similar to Argon2, you need to store the salt and other parameters used during the original hashing.

Example:

Java
 
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.spec.KeySpec;
import java.util.Base64;

public class PBKDF2Hashing {
    public static boolean verifyPassword(String inputPassword, String storedHash, byte[] salt, int iterationCount, int keyLength) throws Exception {
        KeySpec spec = new PBEKeySpec(inputPassword.toCharArray(), salt, iterationCount, keyLength);
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");

        byte[] hash = factory.generateSecret(spec).getEncoded();
        String newHash = Base64.getEncoder().encodeToString(hash);

        return newHash.equals(storedHash);
    }
}


4. Verifying Password with SHA-512

For SHA-512, you must store the salt used for hashing. Then, use the same salt to hash the input password and compare the hashes.

Example:

Java
 
import java.security.MessageDigest;
import java.util.Base64;

public class SHA512Hashing {
    public static boolean verifyPassword(String inputPassword, String storedHash, byte[] salt) throws Exception {
        MessageDigest md = MessageDigest.getInstance("SHA-512");
        md.update(salt);

        byte[] hashedInputPassword = md.digest(inputPassword.getBytes());
        String newHash = Base64.getEncoder().encodeToString(hashedInputPassword);

        return newHash.equals(storedHash);
    }
}


Important Notes

  • For BCrypt, Argon2, and PBKDF2, it's crucial to use their respective library methods for verification when available, as these handle the comparison securely.
  • For SHA-512, and generally for other hashing algorithms without built-in verification methods, ensure you implement secure comparison to avoid timing attacks.
  • Always securely store the salt and, when necessary, other parameters (like iteration count) alongside the hashed password.

Adoption Across Languages and Frameworks

BCrypt Support

Argon2 Support

PBKDF2 Support

Choosing the Right Algorithm

Conclusion

As cyber threats evolve, so must our methods of protecting sensitive information. Employing modern password hashing techniques like BCrypt, Argon2, and PBKDF2 is essential for safeguarding user data. These methods provide robust defense mechanisms against the most common password-cracking strategies, ensuring that even if data breaches occur, the impact on password integrity is minimized. Developers and security professionals must stay informed about the latest advancements in cryptographic practices and continuously update their security measures accordingly.

Check sources and tests at my GitHub repository. 

 

 

 

 

Top