Main Tutorials

Java Password Hashing with Argon2

JNA Argon2

Argon2 was the winner of the Password Hashing Competition in July 2015, a one-way hashing function that is intentionally resource (CPU, memory, etc) intensive. In Argon2, we can configure the length of the salt, the length of the generated hash, iterations, memory cost, and CPU cost to control the resources that are needed to hash a password.

The Argon2 algorithm has three variants:

  • Argon2d, maximizes resistance to GPU cracking attacks, suitable for Cryptocurrency.
  • Argon2i, optimized to resist side-channel attacks, suitable for password hashing.
  • Argon2id, hybrid version, if not sure, picks this.

The Argon2 algorithm accepts five configurable parameters:

  • Salt length — The length of the random salt, recommends 16 bytes.
  • Key length — The length of the generated hash, recommends 16 bytes, but the majority prefer 32 bytes.
  • Iterations — Number of iterations, affect the time cost.
  • Memory — The amount of memory used by the algorithm (in kibibytes, 1k = 1024 bytes), affect the memory cost.
  • Parallelism — Number of threads (or lanes) used by the algorithm, affect the degree of parallelism.

Read this Argon2 whitepaper.

Why need slow password hashing?

The modern hardware (CPU and GPU) is getting better and cheaper. The consumer CPU, like AMD Ryzen Threadripper, is increasing the cores every year, for example, AMD 3990X has 64 cores, 128 threads.

Also, thanks to the rise of Cryptocurrency mining, GPU keeps on evolving, FPGA or dedicated ASIC can perform billions of hash calculation a second.

Quantum computing, is still too early to mention this, but sooner or later, we will enter the quantum computing era, and no one know how much faster quantum computers can perform at hash speed.

Moore’s law, fast forward ten years, the hash speed will grow exponentially, highly possible we can decode an MD5, SHA1, SHA-256, SHA-512 or BLAKE2 password hashed within a second or even faster (even salting no help).

In a nutshell, all fast hashing algorithms are not suitable for password hashing, and we need some slow hash and resource-intensive algorithms like Bcrypt, Scrypt, or Argon2.

In Java, we can use the following libraries to perform an Argon2 password hashing.

1. Java Argon2 Password Hashing – argon2-jvm

This argon2-jvm, internally uses Java Native Access (JNA) to call the Argon2 C library.

JNA Argon2

Argon2 C libraries

1.1 This argon2-jvm is available at the Maven central repository.

pom.xml

  <dependency>
      <groupId>de.mkammerer</groupId>
      <artifactId>argon2-jvm</artifactId>
      <version>2.7</version>
  </dependency>

1.2 The default Argon2Factory.create() returns an argon2i variant, with 16 bytes salt and 32 bytes hash length.


  // Argon2Types.ARGON2i
  // salt 16 bytes
  // Hash length 32 bytes
  Argon2 argon2 = Argon2Factory.create();

We can customize the Argon2 variant, length of the salt, and the length of the generated hash.


  // Argon2Types.ARGON2id
  // salt 32 bytes
  // Hash length 64 bytes
  Argon2 argon2 = Argon2Factory.create(
        Argon2Factory.Argon2Types.ARGON2id,
        32,
        64);

1.3 Review the primary hash method; it takes four parameters, iterations, memory, parallelism, and password to hash.

Argon2.java

package de.mkammerer.argon2;

public interface Argon2 {

    /**
     * Hashes a password.
     * <p>
     * Uses UTF-8 encoding.
     *
     * @param iterations  Number of iterations
     * @param memory      Sets memory usage to x kibibytes
     * @param parallelism Number of threads and compute lanes
     * @param password    Password to hash
     * @return Hashed password.
     */
    String hash(int iterations, int memory, int parallelism, char[] password);

    //...

1.4 This Java example supplies the following inputs to perform an Argon2 password hashing.

  1. Variants = argon2i (default)
  2. Salt = 16 bytes, 128-bit (default)
  3. Hash length = 32 bytes, 256-bit (default)
  4. Iterations = 10
  5. Memory = 65536k, 64M
  6. Parallelism = 1

We run the below code four times on our test computer, the main spec is AMD 3900X 12-Core, 16M memory, and it takes around 450-500 ms to hash a password with Argon2 algorithm.

PasswordArgon2Jvm.java

package com.mkyong.crypto.password;

import de.mkammerer.argon2.Argon2;
import de.mkammerer.argon2.Argon2Factory;

import java.time.Instant;
import java.time.temporal.ChronoUnit;

public class PasswordArgon2Jvm {

    public static void main(String[] args) {

        // default argon2i, salt 16 bytes, hash length 32 bytes.
        Argon2 argon2 = Argon2Factory.create();

        char[] password = "Hello World".toCharArray();

        Instant start = Instant.now();  // start timer

        try {
            // iterations = 10
            // memory = 64m
            // parallelism = 1
            String hash = argon2.hash(22, 65536, 1, password);
            System.out.println(hash);

            // argon2 verify hash
            /*if (argon2.verify(hash, password)) {
                System.out.println("Hash matches password.");
            }*/

            //int iterations = Argon2Helper.findIterations(argon2, 1000, 65536, 1);
            //System.out.println(iterations);

        } finally {
            // Wipe confidential data
            argon2.wipeArray(password);
        }

        Instant end = Instant.now();    // end timer

        System.out.println(String.format(
                "Hashing took %s ms",
                ChronoUnit.MILLIS.between(start, end)
        ));

    }

}

Output. The output is base64 encoded, which consists of Argon2 variants, Argon2 version $v, memory cost $m, iterations $t, parallelism (lane) $p, 16 bytes salt and Argon2 generated hash.

Terminal

# 1st time
$argon2i$v=19$m=65536,t=10,p=1$cGjkgKPK111PPp7t2VEQrA$eowEcB27XAH9wSC1oUjaGuW0jA1iQSmaL4cs7W2Vd0k
Hashing took 500 ms

# 2nd time
$argon2i$v=19$m=65536,t=10,p=1$YkDFUQRhJGa0KjEWusHYQQ$t8IPgKRRFCMkb84cU1PB8JlS3aa+hTQfzFmmbz5omnk
Hashing took 489 ms

# 3rd time
$argon2i$v=19$m=65536,t=10,p=1$h2X+NkgqWtnpnfoQq2NDaw$xWqR9NaL7t6SaJJLiXeFtrGFNp4j08FJJIuTXe0oRiI
Hashing took 457 ms

# 4th time
$argon2i$v=19$m=65536,t=10,p=1$/bY1iOq+C8sRpIGcrwb2fQ$d1Ed4lLAeVrxBjFxINEbC4wNl0FZ2lZ2JsNkJjJkODA
Hashing took 507 ms

1.5 We can provide different inputs to increase the amount of time of password hashing, let said we want the hash function takes at most 1 second, giving 64m and one thread, we can use Argon2Helper.findIterations to find out the optimized iterations.


  // 1000 = Time in ms, we want this 1 second
  // 65536 = Memory cost, 64Mb
  // 1 = parallelism
  int iterations = Argon2Helper.findIterations(argon2, 1000, 65536, 1);
  System.out.println(iterations);

Output


22

Rerun the program with the suggested 22 iterations. Now, the Argon2 password hashing takes around 1 second.

PasswordArgon2Jvm.java

  String hash = argon2.hash(22, 65536, 1, password);
  System.out.println(hash);

Output

Terminal

# 1st
$argon2i$v=19$m=65536,t=22,p=1$LqszYnhGhlM6AW3ehXhXmA$hgFiUxZUbgdodrIOUHhUzPdiWecYYFmHdFPQEf6beBc
Hashing took 1004 ms

# 2nd
$argon2i$v=19$m=65536,t=22,p=1$kySDkzqRkEr748trey63Dg$OsKcqvoK/Y5pywATXw0P8RmKeMAzurNsgbGlmnw8Svs
Hashing took 991 ms

2. Java Argon2 Password Hashing – Spring Security

2.1 In Spring Security, we can use Argon2PasswordEncoder to perform an Argon2 password hashing. The implementation of the Argon2PasswordEncoder requires BouncyCastle.

pom.xml

    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-crypto</artifactId>
        <version>5.3.2.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.2</version>
    </dependency>

    <dependency>
        <groupId>org.bouncycastle</groupId>
        <artifactId>bcpkix-jdk15on</artifactId>
        <version>1.65</version>
    </dependency>

bouncy castle api

2.2 The Argon2PasswordEncoder, internally uses BouncyCastle APIs like Argon2Parameters and Argon2BytesGenerator to provide Argon2 hashing.

Argon2PasswordEncoder.java

package org.springframework.security.crypto.argon2;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.bouncycastle.crypto.generators.Argon2BytesGenerator;
import org.bouncycastle.crypto.params.Argon2Parameters;
import org.springframework.security.crypto.keygen.BytesKeyGenerator;
import org.springframework.security.crypto.keygen.KeyGenerators;
import org.springframework.security.crypto.password.PasswordEncoder;

public class Argon2PasswordEncoder implements PasswordEncoder {

      private static final int DEFAULT_SALT_LENGTH = 16;
      private static final int DEFAULT_HASH_LENGTH = 32;
      private static final int DEFAULT_PARALLELISM = 1;
      private static final int DEFAULT_MEMORY = 1 << 12;
      private static final int DEFAULT_ITERATIONS = 3;

      //...

      public Argon2PasswordEncoder(int saltLength, int hashLength,
        int parallelism, int memory, int iterations) {

    		this.hashLength = hashLength;
    		this.parallelism = parallelism;
    		this.memory = memory;
    		this.iterations = iterations;

    		this.saltGenerator = KeyGenerators.secureRandom(saltLength);
    	}

    	public Argon2PasswordEncoder() {
    		this(DEFAULT_SALT_LENGTH, DEFAULT_HASH_LENGTH,
          DEFAULT_PARALLELISM, DEFAULT_MEMORY, DEFAULT_ITERATIONS);
    	}

      @Override
    	public String encode(CharSequence rawPassword) {
    		byte[] salt = saltGenerator.generateKey();
    		byte[] hash = new byte[hashLength];

    		Argon2Parameters params = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_id).
    				withSalt(salt).
    				withParallelism(parallelism).
    				withMemoryAsKB(memory).
    				withIterations(iterations).
    				build();
    		Argon2BytesGenerator generator = new Argon2BytesGenerator();
    		generator.init(params);
    		generator.generateBytes(rawPassword.toString().toCharArray(), hash);

    		return Argon2EncodingUtils.encode(hash, params);
    	}

      //...

2.3 This Java example uses Argon2PasswordEncoder to perform an Argon2 password hashing, using default inputs:

  1. Variants = argon2id
  2. Salt = 16 bytes, 128-bit
  3. Hash length = 32 bytes, 256-bit
  4. Iteration = 3
  5. Memory = 1 << 12, or 2 ^ 12, 4096k
  6. Parallelism = 1
PasswordArgon2SpringSecurity.java

package com.mkyong.crypto.password;

import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;

import java.time.Instant;
import java.time.temporal.ChronoUnit;

public class PasswordArgon2SpringSecurity {

    public static void main(String[] args) {

        Argon2PasswordEncoder encoder = new Argon2PasswordEncoder();

        String password = "Hello World";

        Instant start = Instant.now();  // start timer

        String hash = encoder.encode(password);
        System.out.println(hash);

        // argon2 verify hash
        /*if (encoder.matches("Hello World", hash)) {
            System.out.println("match");
        }*/

        Instant end = Instant.now();    // end timer

        System.out.println(String.format(
                "Hashing took %s ms",
                ChronoUnit.MILLIS.between(start, end)
        ));

    }

}

Output

Terminal

# 1st
$argon2id$v=19$m=4096,t=3,p=1$iurr6y6xk2X7X/YVOEQXBg$ti9/be9VgbXtJWpm1hoYyLm8V0wBGr+dxu9X+PFbpZI
Hashing took 176 ms

# 2nd
$argon2id$v=19$m=4096,t=3,p=1$vnOEfUC3oZ3sVBj/yKG/4g$LdVFmw9N5D49tuJiYT0LGZ8YOqYetqz5UzDyku+7PRs
Hashing took 156 ms

2.4 The inputs are configurable.

Argon2PasswordEncoder.java

    public Argon2PasswordEncoder(int saltLength, int hashLength, int parallelism, int memory, int iterations) {
  		this.hashLength = hashLength;
  		this.parallelism = parallelism;
  		this.memory = memory;
  		this.iterations = iterations;

  		this.saltGenerator = KeyGenerators.secureRandom(saltLength);
  	}

For example,


  // int saltLength, int hashLength, int parallelism, int memory, int iterations
  Argon2PasswordEncoder encoder = new Argon2PasswordEncoder(16, 32, 1, 65536, 10);
  1. Variants = argon2id
  2. Salt = 16 bytes, 128-bit
  3. Hash length = 32 bytes, 256-bit
  4. Iteration = 10
  5. Memory = 1 << 16, or 2 ^ 16, 65536k, 64M
  6. Parallelism = 1

The Argon2PasswordEncoder doesn’t allow to change the Argon2’s variants.

3. FAQs

3.1 What are the Argon2 recommended parameters?

Besides 16 bytes salt and 32 bytes key length, the rest of the parameters depends on the server capacity. Run the Argon2 password hashing at the production server and fine-tune the iterations, threads, memory, and time that each call can afford. Generally, Argon2 authentication takes 0.5ms to 1 second is recommended.

Download Source Code

$ git clone https://github.com/mkyong/core-java

$ cd java-crypto

References

About Author

author image
Founder of Mkyong.com, love Java and open source stuff. Follow him on Twitter. If you like my tutorials, consider make a donation to these charities.

Comments

Subscribe
Notify of
5 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments
Nicole
1 year ago

Tried also Password4j which is lighter, supports all the algorithm recommended by OWASP and helps managing salt, pepper and converts legacy hash in stronger ones.

Anatoly
1 year ago

Awesome! Very imformative, first time at your blog and I will be returning to read more articles!

Swanand D.
2 years ago

Is there any downside/side-effects for Spring Security Crypto, as it uses BouncyCastle instead?

Last edited 2 years ago by Swanand D.
Daniel de Jesus
2 years ago

It is necessary to download version 2.10.1, because version 2.7 no longer works

Daniel de Jesus
3 years ago

Thanks a lot! Its very useful.