当前位置: 首页 > news >正文

使用 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密码(即使加盐也无济于事)。

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

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

  • argon2-jvm
  • Spring Security Argon2PasswordEncoder

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

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

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

pom.xml

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

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


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

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


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

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

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 此 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

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)
        ));

    }

}

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

终端

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


  // 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);

输出


22

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

PasswordArgon2Jvm.java

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

输出

终端

# 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 密码散列 - Spring Security

2.1 在 Spring Security 中,我们可以使用Argon2PasswordEncoderArgon2 进行密码散列。的实现Argon2PasswordEncoder需要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>

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

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 此 Java 示例用于Argon2PasswordEncoder执行 Argon2 密码散列,使用默认输入:

  1. 变体 =argon2id
  2. Salt = 16 字节,128 位
  3. 哈希长度 = 32 字节,256 位
  4. 迭代 = 3
  5. 内存 = 1 << 12, 或 2 ^ 12, 4096k
  6. 并行度 = 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)
        ));

    }

}

输出

终端

# 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 输入是可配置的。

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);
  	}

例如,


  // int saltLength, int hashLength, int parallelism, int memory, int iterations
  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 秒。

下载源代码

$ git clone GitHub - mkyong/core-java: List of core Java source code

$ cd java-crypto

参考

  • 密码哈希 Argon2
  • Argon2 白皮书
  • 维基百科-Argon2
  • 维基百科,免费的百科全书
  • 维基百科,免费的百科全书
  • Argon2-JVM
  • 充气城堡
  • Spring Security 密码存储
  • Argon2PasswordEncoder
  • 密码哈希竞赛
  • Java 8 – 期间和持续时间示例

相关文章:

  • 基于多次傅里叶变换算法的快速相位解包裹算法研究
  • Mybatis-Plus用纯注解搞定一对多查询
  • 6.CF431E Chemistry Experiment 权值线段树+二分
  • 基于RFID技术的智能书架系统
  • 1014 Circles of Friends
  • Linux 下进程间通讯之内存映射详解
  • ROS官方教程知识点总结[低阶阶段]
  • Linux常见命令汇总-基于CentOS7
  • 让软件集成为您的业务创造更多价值
  • 猿创征文 | 云服务器部署——将项目部署到云服务器上
  • PET-MRI医学图像融合与混合神经胶质瘤分类模型
  • RACV2022观点集锦 | 视觉基础模型
  • 【GNN报告】复旦大学许嘉蓉:基于图数据的鲁棒机器学习
  • 树链剖分模板
  • 华为防火墙基础自学系列 | Hub Spoke IPsec VdPdNd
  • 《用数据讲故事》作者Cole N. Knaflic:消除一切无效的图表
  • 【Linux系统编程】快速查找errno错误码信息
  • Bytom交易说明(账户管理模式)
  • C++类中的特殊成员函数
  • cookie和session
  • Intervention/image 图片处理扩展包的安装和使用
  • Koa2 之文件上传下载
  • Linux链接文件
  • Odoo domain写法及运用
  • PHP面试之三:MySQL数据库
  • Spring Cloud中负载均衡器概览
  • Vue2.0 实现互斥
  • 聊聊flink的BlobWriter
  • 前端面试总结(at, md)
  • 让你成为前端,后端或全栈开发程序员的进阶指南,一门学到老的技术
  • 使用API自动生成工具优化前端工作流
  • 使用Gradle第一次构建Java程序
  • 微信小程序实战练习(仿五洲到家微信版)
  • 与 ConTeXt MkIV 官方文档的接驳
  • 在electron中实现跨域请求,无需更改服务器端设置
  • 不要一棍子打翻所有黑盒模型,其实可以让它们发挥作用 ...
  • ​创新驱动,边缘计算领袖:亚马逊云科技海外服务器服务再进化
  • # Maven错误Error executing Maven
  • $(selector).each()和$.each()的区别
  • (1)(1.19) TeraRanger One/EVO测距仪
  • (39)STM32——FLASH闪存
  • (附源码)springboot人体健康检测微信小程序 毕业设计 012142
  • (附源码)ssm高校运动会管理系统 毕业设计 020419
  • (紀錄)[ASP.NET MVC][jQuery]-2 純手工打造屬於自己的 jQuery GridView (含完整程式碼下載)...
  • (四)c52学习之旅-流水LED灯
  • (一)Java算法:二分查找
  • (一)SpringBoot3---尚硅谷总结
  • (正则)提取页面里的img标签
  • ..回顾17,展望18
  • .net 4.0 A potentially dangerous Request.Form value was detected from the client 的解决方案
  • .net 7 上传文件踩坑
  • .NET Core 2.1路线图
  • .NET Core 通过 Ef Core 操作 Mysql
  • .Net Core和.Net Standard直观理解
  • .NET NPOI导出Excel详解