• 使用 Argon2 的 Java 密码散列


    Argon2是 2015 年 7 月密码哈希竞赛的获胜者,这是一种有意占用资源(CPU、内存等)的单向哈希函数。在 Argon2 中,我们可以配置盐的长度、生成的哈希长度、迭代次数、内存成本和 CPU 成本,以控制哈希密码所需的资源。

    Argon2 算法有三种变体:

    • Argon2d,最大限度地抵抗GPU破解攻击,适用于加密货币。
    • Argon2i,优化以抵抗侧信道攻击,适用于密码散列。
    • Argon2id,混合版本,如果不确定,选择这个。

    Argon2 算法接受五个可配置参数:

    • 盐长度——随机盐的长度,推荐16字节。
    • 密钥长度——生成哈希的长度,推荐 16 字节,但大多数人更喜欢 32 字节。
    • 迭代——迭代次数,影响时间成本。
    • 内存——算法使用的内存量(以千字节为单位,1k = 1024 字节)会影响内存成本。
    • 并行度——算法使用的线程(或通道)数量,影响并行度。

    阅读此Argon2 白皮书

    为什么需要慢速密码散列?

    现代硬件(CPU 和 GPU)越来越好,越来越便宜。消费级 CPU,如 AMD Ryzen Threadripper,每年都在增加内核,例如 AMD 3990X 有 64 个内核,128 个线程。

    此外,由于加密货币挖矿的兴起 GPU 不断发展,FPGA或专用ASIC可以每秒执行数十亿次哈希计算

    量子计算,现在说这个还为时过早,但迟早我们会进入量子计算时代,没有人知道量子计算机在哈希速度上能执行多快。

    摩尔定律,快进十年,哈希速度将呈指数级增长,很有可能我们可以在一秒钟甚至更快的时间内解码一个MD5SHA1SHA-256,SHA-512BLAKE2密码(即使加盐也无济于事)。

    简而言之,所有快速散列算法都不适用于密码散列,我们需要一些慢速散列和资源密集型算法,如BcryptScryptArgon2.

    在 Java 中,我们可以使用以下库来执行 Argon2 密码散列。

    1. Java Argon2 密码散列 - argon2-jvm

    这个argon2-jvm内部使用Java Native Access (JNA)来调用Argon2 C library

    1.1 这argon2-jvm在 Maven 中央存储库中可用。

    pom.xml
    1. <dependency>
    2. <groupId>de.mkammerergroupId>
    3. <artifactId>argon2-jvmartifactId>
    4. <version>2.7version>
    5. dependency>

    1.2 默认Argon2Factory.create()返回一个argon2i变体,具有 16 字节的盐和 32 字节的哈希长度。

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

    我们可以自定义 Argon2 变体、盐的长度以及生成的哈希的长度。

    1. // Argon2Types.ARGON2id
    2. // salt 32 bytes
    3. // Hash length 64 bytes
    4. Argon2 argon2 = Argon2Factory.create(
    5. Argon2Factory.Argon2Types.ARGON2id,
    6. 32,
    7. 64);

    1.3 审查主哈希方法;它需要四个参数,迭代次数、内存、并行度和密码来散列。

    Argon2.java
    1. package de.mkammerer.argon2;
    2. public interface Argon2 {
    3. /**
    4. * Hashes a password.
    5. *

    6. * Uses UTF-8 encoding.
    7. *
    8. * @param iterations Number of iterations
    9. * @param memory Sets memory usage to x kibibytes
    10. * @param parallelism Number of threads and compute lanes
    11. * @param password Password to hash
    12. * @return Hashed password.
    13. */
    14. String hash(int iterations, int memory, int parallelism, char[] password);
    15. //...

    1.4 此 Java 示例提供以下输入来执行 Argon2 密码散列。

    1. 变体 = argon2i(默认)
    2. Salt = 16 字节,128 位(默认)
    3. 哈希长度 = 32 字节,256 位(默认)
    4. 迭代次数 = 10
    5. 内存 = 65536k, 64M
    6. 并行度 = 1

    我们在测试计算机上运行以下代码四次,主要规格是 AMD 3900X 12 核,16M 内存,使用 Argon2 算法散列密码大约需要 450-500 毫秒。

    PasswordArgon2Jvm.java
    1. package com.mkyong.crypto.password;
    2. import de.mkammerer.argon2.Argon2;
    3. import de.mkammerer.argon2.Argon2Factory;
    4. import java.time.Instant;
    5. import java.time.temporal.ChronoUnit;
    6. public class PasswordArgon2Jvm {
    7. public static void main(String[] args) {
    8. // default argon2i, salt 16 bytes, hash length 32 bytes.
    9. Argon2 argon2 = Argon2Factory.create();
    10. char[] password = "Hello World".toCharArray();
    11. Instant start = Instant.now(); // start timer
    12. try {
    13. // iterations = 10
    14. // memory = 64m
    15. // parallelism = 1
    16. String hash = argon2.hash(22, 65536, 1, password);
    17. System.out.println(hash);
    18. // argon2 verify hash
    19. /*if (argon2.verify(hash, password)) {
    20. System.out.println("Hash matches password.");
    21. }*/
    22. //int iterations = Argon2Helper.findIterations(argon2, 1000, 65536, 1);
    23. //System.out.println(iterations);
    24. } finally {
    25. // Wipe confidential data
    26. argon2.wipeArray(password);
    27. }
    28. Instant end = Instant.now(); // end timer
    29. System.out.println(String.format(
    30. "Hashing took %s ms",
    31. ChronoUnit.MILLIS.between(start, end)
    32. ));
    33. }
    34. }

    输出。输出是 base64 编码的,由 Argon2 变体、Argon2 版本$v、内存成本$m、迭代$t、并行度(通道)$p、16 字节盐和 Argon2 生成的哈希组成。

    终端
    1. # 1st time
    2. $argon2i$v=19$m=65536,t=10,p=1$cGjkgKPK111PPp7t2VEQrA$eowEcB27XAH9wSC1oUjaGuW0jA1iQSmaL4cs7W2Vd0k
    3. Hashing took 500 ms
    4. # 2nd time
    5. $argon2i$v=19$m=65536,t=10,p=1$YkDFUQRhJGa0KjEWusHYQQ$t8IPgKRRFCMkb84cU1PB8JlS3aa+hTQfzFmmbz5omnk
    6. Hashing took 489 ms
    7. # 3rd time
    8. $argon2i$v=19$m=65536,t=10,p=1$h2X+NkgqWtnpnfoQq2NDaw$xWqR9NaL7t6SaJJLiXeFtrGFNp4j08FJJIuTXe0oRiI
    9. Hashing took 457 ms
    10. # 4th time
    11. $argon2i$v=19$m=65536,t=10,p=1$/bY1iOq+C8sRpIGcrwb2fQ$d1Ed4lLAeVrxBjFxINEbC4wNl0FZ2lZ2JsNkJjJkODA
    12. Hashing took 507 ms

    1.5 我们可以提供不同的输入来增加密码哈希的时间,假设我们希望哈希函数最多需要1秒,给64m和一个线程,我们可以Argon2Helper.findIterations用来找出优化的迭代。

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

    输出

    1. 22

    使用建议的 22 次迭代重新运行程序。现在,Argon2 密码散列大约需要 1 秒。

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

    输出

    终端
    1. # 1st
    2. $argon2i$v=19$m=65536,t=22,p=1$LqszYnhGhlM6AW3ehXhXmA$hgFiUxZUbgdodrIOUHhUzPdiWecYYFmHdFPQEf6beBc
    3. Hashing took 1004 ms
    4. # 2nd
    5. $argon2i$v=19$m=65536,t=22,p=1$kySDkzqRkEr748trey63Dg$OsKcqvoK/Y5pywATXw0P8RmKeMAzurNsgbGlmnw8Svs
    6. Hashing took 991 ms

    2. Java Argon2 密码散列 - Spring Security

    2.1 在 Spring Security 中,我们可以使用Argon2PasswordEncoderArgon2 进行密码散列。的实现Argon2PasswordEncoder需要BouncyCastle

    pom.xml
    1. <dependency>
    2. <groupId>org.springframework.securitygroupId>
    3. <artifactId>spring-security-cryptoartifactId>
    4. <version>5.3.2.RELEASEversion>
    5. dependency>
    6. <dependency>
    7. <groupId>commons-logginggroupId>
    8. <artifactId>commons-loggingartifactId>
    9. <version>1.2version>
    10. dependency>
    11. <dependency>
    12. <groupId>org.bouncycastlegroupId>
    13. <artifactId>bcpkix-jdk15onartifactId>
    14. <version>1.65version>
    15. dependency>

    2.2 Argon2PasswordEncoder, 内部使用和之BouncyCastle类的 API来提供 Argon2 散列。Argon2ParametersArgon2BytesGenerator

    Argon2PasswordEncoder.java
    1. package org.springframework.security.crypto.argon2;
    2. import org.apache.commons.logging.Log;
    3. import org.apache.commons.logging.LogFactory;
    4. import org.bouncycastle.crypto.generators.Argon2BytesGenerator;
    5. import org.bouncycastle.crypto.params.Argon2Parameters;
    6. import org.springframework.security.crypto.keygen.BytesKeyGenerator;
    7. import org.springframework.security.crypto.keygen.KeyGenerators;
    8. import org.springframework.security.crypto.password.PasswordEncoder;
    9. public class Argon2PasswordEncoder implements PasswordEncoder {
    10. private static final int DEFAULT_SALT_LENGTH = 16;
    11. private static final int DEFAULT_HASH_LENGTH = 32;
    12. private static final int DEFAULT_PARALLELISM = 1;
    13. private static final int DEFAULT_MEMORY = 1 << 12;
    14. private static final int DEFAULT_ITERATIONS = 3;
    15. //...
    16. public Argon2PasswordEncoder(int saltLength, int hashLength,
    17. int parallelism, int memory, int iterations) {
    18. this.hashLength = hashLength;
    19. this.parallelism = parallelism;
    20. this.memory = memory;
    21. this.iterations = iterations;
    22. this.saltGenerator = KeyGenerators.secureRandom(saltLength);
    23. }
    24. public Argon2PasswordEncoder() {
    25. this(DEFAULT_SALT_LENGTH, DEFAULT_HASH_LENGTH,
    26. DEFAULT_PARALLELISM, DEFAULT_MEMORY, DEFAULT_ITERATIONS);
    27. }
    28. @Override
    29. public String encode(CharSequence rawPassword) {
    30. byte[] salt = saltGenerator.generateKey();
    31. byte[] hash = new byte[hashLength];
    32. Argon2Parameters params = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_id).
    33. withSalt(salt).
    34. withParallelism(parallelism).
    35. withMemoryAsKB(memory).
    36. withIterations(iterations).
    37. build();
    38. Argon2BytesGenerator generator = new Argon2BytesGenerator();
    39. generator.init(params);
    40. generator.generateBytes(rawPassword.toString().toCharArray(), hash);
    41. return Argon2EncodingUtils.encode(hash, params);
    42. }
    43. //...

    2.3 此 Java 示例用于Argon2PasswordEncoder执行 Argon2 密码散列,使用默认输入:

    1. 变体 =argon2id
    2. Salt = 16 字节,128 位
    3. 哈希长度 = 32 字节,256 位
    4. 迭代 = 3
    5. 内存 = 1 << 12, 或 2 ^ 12, 4096k
    6. 并行度 = 1
    PasswordArgon2SpringSecurity.java
    1. package com.mkyong.crypto.password;
    2. import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
    3. import java.time.Instant;
    4. import java.time.temporal.ChronoUnit;
    5. public class PasswordArgon2SpringSecurity {
    6. public static void main(String[] args) {
    7. Argon2PasswordEncoder encoder = new Argon2PasswordEncoder();
    8. String password = "Hello World";
    9. Instant start = Instant.now(); // start timer
    10. String hash = encoder.encode(password);
    11. System.out.println(hash);
    12. // argon2 verify hash
    13. /*if (encoder.matches("Hello World", hash)) {
    14. System.out.println("match");
    15. }*/
    16. Instant end = Instant.now(); // end timer
    17. System.out.println(String.format(
    18. "Hashing took %s ms",
    19. ChronoUnit.MILLIS.between(start, end)
    20. ));
    21. }
    22. }

    输出

    终端
    1. # 1st
    2. $argon2id$v=19$m=4096,t=3,p=1$iurr6y6xk2X7X/YVOEQXBg$ti9/be9VgbXtJWpm1hoYyLm8V0wBGr+dxu9X+PFbpZI
    3. Hashing took 176 ms
    4. # 2nd
    5. $argon2id$v=19$m=4096,t=3,p=1$vnOEfUC3oZ3sVBj/yKG/4g$LdVFmw9N5D49tuJiYT0LGZ8YOqYetqz5UzDyku+7PRs
    6. Hashing took 156 ms

    2.4 输入是可配置的。

    Argon2PasswordEncoder.java
    1. public Argon2PasswordEncoder(int saltLength, int hashLength, int parallelism, int memory, int iterations) {
    2. this.hashLength = hashLength;
    3. this.parallelism = parallelism;
    4. this.memory = memory;
    5. this.iterations = iterations;
    6. this.saltGenerator = KeyGenerators.secureRandom(saltLength);
    7. }

    例如,

    1. // int saltLength, int hashLength, int parallelism, int memory, int iterations
    2. Argon2PasswordEncoder encoder = new Argon2PasswordEncoder(16, 32, 1, 65536, 10);
    1. 变体 =argon2id
    2. Salt = 16 字节,128 位
    3. 哈希长度 = 32 字节,256 位
    4. 迭代 = 10
    5. 内存 = 1 << 16,或 2 ^ 16、65536k、64M
    6. 并行度 = 1

    Argon2PasswordEncoder不允许更改 Argon2 的变体。

    3. 常见问题

    3.1 Argon2推荐参数是什么?

    除了 16 字节盐和 32 字节密钥长度外,其余参数取决于服务器容量。在生产服务器上运行 Argon2 密码散列,并微调迭代、线程、内存和每次调用可以承受的时间。一般来说,Argon2 认证需要 0.5ms 到 1 秒。

    下载源代码

    参考

  • 相关阅读:
    神经网络编程的34个案例,神经网络程序实例100篇
    前端瀑布流怎么布局
    Springboot 启动报错@spring.active@解析错误
    告警:线上慎用 BigDecimal ,坑的差点被开了
    SSR 实战:官网开发指南
    Linux学习笔记7-文件IO编程(一)
    【MySQL】开启 canal同步MySQL增量数据到ES
    自动化测试生命周期的六个阶段
    解决selenium加载网页过慢影响程序运行时间的问题
    第三版全球干旱指数和潜在蒸散数据发布
  • 原文地址:https://blog.csdn.net/allway2/article/details/126733119