DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • Securing Your Applications With Spring Security
  • An Overview of the Top 10 Programming Languages Used in the World
  • An Overview of Programming Languages
  • A Beginner's Guide to Back-End Development

Trending

  • Data Lake vs. Warehouse vs. Lakehouse vs. Mart: Choosing the Right Architecture for Your Business
  • AI-Driven Root Cause Analysis in SRE: Enhancing Incident Resolution
  • Cloud Security and Privacy: Best Practices to Mitigate the Risks
  • Immutable Secrets Management: A Zero-Trust Approach to Sensitive Data in Containers
  1. DZone
  2. Coding
  3. Java
  4. Secure Password Hashing in Java: Best Practices and Code Examples

Secure Password Hashing in Java: Best Practices and Code Examples

This article covers secure password hashing in Java using modern algorithms like BCrypt, Argon2, and PBKDF2 with salting and computational intensity for better security.

By 
Wallace Espindola user avatar
Wallace Espindola
·
Dec. 22, 23 · Analysis
Likes (4)
Comment
Save
Tweet
Share
20.0K Views

Join the DZone community and get the full member experience.

Join For Free

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

  • Multiple Iterations: These algorithms apply the hashing function many times (thousands or millions of iterations). Each iteration requires time to process. For example, if a single SHA-256 hash takes a fraction of a millisecond, repeating this process thousands of times for each password significantly increases the overall computation time.
  • Adjustable Work Factor: In algorithms like BCrypt, there is a work factor or cost parameter that determines how many times the hashing loop runs. As hardware gets faster, this factor can be increased to ensure that the hashing process does not become too quick.

2. Memory Intensive Operations

  • Increased Memory Usage: Some algorithms, like Argon2, are designed to use a significant amount of memory in addition to CPU resources. This makes it more difficult for attackers to parallelize attacks using GPUs or custom hardware, which often have limited high-speed memory available per processing unit.

3. Built-in Salting

  • Unique Salts for Each Password:Modern hashing methods automatically generate a unique salt for each password. A salt is a random value that is added to the password before hashing. This means that even if two users have the same password, their hashes will be different. Salting also prevents the use of precomputed hash tables (rainbow tables) for reversing the hashes.

Effectiveness Against Different Types of Attacks

  • Brute-Force Attacks: The time and resource intensity of these algorithms make brute-force attacks (trying every possible password combination) impractical, especially for strong passwords.
  • Rainbow Table Attacks: Since each password hash is salted with a unique value, precomputed tables of hashes become useless.
  • Custom Hardware Attacks: The memory and processing requirements make it more difficult and expensive for attackers to use specialized hardware, like ASICs or GPUs, to speed up the cracking process.

Real-World Impact

  • Legitimate User Experience: For legitimate users, the extra time taken by these hashing algorithms (usually a fraction of a second) is negligible during login or account creation.
  • Attacker Experience: For an attacker trying to crack passwords, this time adds up quickly. What might have taken days with older hashing methods could take years with modern algorithms, effectively rendering brute-force attacks impractical for strong passwords.

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

  • Languages: JavaScript/Node.js, Python, Java, Ruby, PHP, C#/.NET, Go
  • Frameworks: Spring Security, Ruby on Rails, Django, Express

Argon2 Support

  • Languages: C, Python, JavaScript/Node.js, PHP, Ruby, Java, Rust
  • Frameworks: Laravel, Symfony, Phoenix

PBKDF2 Support

  • Languages: Java, Python, C#/.NET, Ruby, PHP, JavaScript/Node.js, Go
  • Frameworks: Spring Framework, ASP.NET, Django

Choosing the Right Algorithm

  • Security Needs: Argon2 offers the highest security, especially against GPU attacks, but requires a more sophisticated configuration.
  • Compatibility and Legacy Systems: PBKDF2 is widely supported and may be the choice for systems needing to comply with certain standards or legacy compatibility.
  • Balance and Ease of Use: BCrypt provides a good balance between security and performance, is easy to implement, and is widely supported in many frameworks and languages.

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. 

PHP Ruby (programming language) Spring Security Java (programming language)

Published at DZone with permission of Wallace Espindola. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Securing Your Applications With Spring Security
  • An Overview of the Top 10 Programming Languages Used in the World
  • An Overview of Programming Languages
  • A Beginner's Guide to Back-End Development

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!