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

Hutool 秒速实现 2FA 两步验证

前言

随着网络安全威胁的日益复杂,传统的用户名和密码认证方式已不足以提供足够的安全保障。为了增强用户账户的安全性,越来越多的应用和服务开始采用多因素认证(MFA)。基于时间的一次性密码(TOTP, Time-based One-Time Password)是多因素认证的一种流行实现方式,它通过生成随时间变化的一次性密码来提供额外的安全层。TOTP 算法由 RFC 6238 定义,广泛应用于各种安全应用中,如 Google Authenticator。

本文展示如何使用 Hutool 工具包实现 TOTP 功能,并结合 Google 的二维码生成工具 来简化密钥分发和用户配置过程。Hutool 是一个功能丰富的 Java 工具类库,提供了大量实用的功能,使得开发者能够更高效地开发应用程序。特别是其内置的支持 TOTP 和 QR Code 生成的功能,为实现多因素认证提供了极大的便利。

引入Hutool的依赖

Hutool 工具包

<!-- Hutool 工具包 -->
<dependency>
	<groupId>cn.hutool</groupId>
	<artifactId>hutool-all</artifactId>
	<version>5.8.22</version>
</dependency>

Google 验证码工具包

<!-- Google 二维码 -->
<dependency>
	<groupId>com.google.zxing</groupId>
	<artifactId>core</artifactId>
	<version>3.5.3</version>
</dependency>

说明:
Hutool 可以按需引入依赖的,这里我没有太过于讲究,直接引入 all 所有包,另外生成 TOTP 的一次性密码需要生成二维码,而 Hutool 刚好就支持Google 的zxing二维码工具包,只不过使用 Hutool 的二维码工具包需要用户手动引入Zxing 包来完成支持

实现代码

package com.hsqyz.web.utils;

import cn.hutool.crypto.digest.otp.TOTP;
import cn.hutool.core.codec.Base32;
import cn.hutool.extra.qrcode.QrCodeUtil;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.time.Instant;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * TOTP 测试类
 */
@Slf4j
public class TOTP_Test {

    public static void main(String[] args) throws InterruptedException {
        // 用户账户名
        String account = "花伤情犹在"; // 用户账户名

        // 指定密钥长度为 15 字节(120 位)
        int numBytes = 15;

        // 生成 QR Code URL
        String qrCodeUrl = TOTP.generateGoogleSecretKey(account, numBytes);
        System.out.println("QR Code URL for Google Authenticator: " + qrCodeUrl);

        // 提取密钥并打印
        String secretKey = extractSecretFromUri(qrCodeUrl);
        System.out.println("提取的密钥是: " + secretKey);

        // 创建 TOTP 对象,默认时间步长为30秒
        byte[] keyBytes = Base32.decode(secretKey); // 将 Base32 编码的字符串转换为字节数组
        TOTP totp = new TOTP(keyBytes);

        // 生成二维码图片并保存为文件
        QrCodeUtil.generate(qrCodeUrl, 200, 200, new File("totp_qrcode.png"));
        System.out.println("二维码已保存为 'totp_qrcode.png'");

        // 开始无限循环,持续打印最新的一次性密码
        while (true) {
            // 获取当前的一次性密码
            int otpCode = totp.generate(Instant.now());
            System.out.println("当前的一次性密码是: " + String.format("%06d", otpCode));

            // 等待 1 秒,以匹配 TOTP 的时间窗口
            Thread.sleep(1000 * 1);
        }
    }

    /**
     * 使用正则表达式从 URI 中提取密钥。
     *
     * @param uri 要解析的 URI 字符串
     * @return 提取的密钥字符串
     */
    private static String extractSecretFromUri(String uri) {
        // 定义正则表达式
        String regex = "secret=([^&]+)";

        // 编译正则表达式
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(uri);

        // 查找并提取密钥
        if (matcher.find()) {
            return matcher.group(1); // 返回第一个捕获组的内容
        } else {
            throw new IllegalArgumentException("URI 中没有找到 secret 参数");
        }
    }

}

运行效果:

使用 Google Authenticator 扫描加入查看:

为什么要使用Base32 进行密钥解码

Base32解码

使用 Base32 解码的原因在于 TOTP(基于时间的一次性密码)算法的工作方式。为了确保生成的一次性密码是安全且可验证的,TOTP 使用共享密钥(secret key)作为输入之一。这个共享密钥通常是通过 Base32 编码进行表示和传输的

Base32 编码的字符集

Base32 编码使用的是一个特定的字符集,包括:

  • 大写字母:A-Z
  • 数字:2-7
  • 填充字符(可选):=

完整字符集

Base32 的完整字符集是:

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 2 3 4 5 6 7

如果密钥包含特殊字符会怎么样?

如果密钥中包含特殊字符并且尝试使用 Base32 编码,可能会导致一系列问题。Base32
编码有其特定的字符集和格式要求,如果密钥中包含不属于 Base32 字符集的字符,编码和解码过程可能会失败或产生不正确的结果。

  1. 编码失败:
    如果密钥中包含不属于上述字符集的字符(例如 @, !, # 等),在进行 Base32 编码时,编码器可能会抛出异常或生成无效的编码字符串。
  2. 解码失败:
    即使编码成功,但生成的 Base32 字符串中包含了非法字符,在解码时也会失败,因为这些字符无法被正确解析为原始字节数组。
  3. 数据损坏:
    如果某些特殊字符被错误地处理或替换,可能会导致最终解码出来的字节数组与原始密钥不一致,从而影响 TOTP 一次性密码的生成和验证。

解决方法
为了避免这些问题,建议确保密钥只包含合法的 Base32 字符。如果您需要生成一个新的密钥,可以使用专门的工具或库来生成符合 Base32 格式的随机密钥。

示例:生成合法的 Base32 密钥

import cn.hutool.core.codec.Base32;
import cn.hutool.crypto.digest.DigestUtil;

public class GenerateValidBase32Key {
    public static void main(String[] args) {
        // 生成随机字节数组(例如 15 字节)
        byte[] randomBytes = DigestUtil.randomBytes(15);

        // 将字节数组编码为 Base32 字符串
        String base32Key = Base32.encode(randomBytes);
        System.out.println("生成的合法 Base32 密钥是: " + base32Key);
    }
}

http://www.kler.cn/a/417858.html

相关文章:

  • pytorch中一个tensor经过多次softmax会有什么变化?
  • 内网穿透步骤
  • cmake一些常用指令
  • 【云原生系列】迁移云上需要考虑哪些问题
  • K8s调度器扩展(scheduler)
  • CENet及多模态情感计算实战
  • How to install mac application by homebrew
  • Oracle12.2 RAC集群管理之增加删除节点(DNS解析)
  • 区块链技术如何改变我们的日常生活?
  • 生产环境中:Flume 与 Prometheus 集成
  • C# 字节流 与 StreamReader 读取 Json 格式文件内容并处理的函数
  • Redis主从架构
  • 远离网上的广告和无用信息,自己动手搭建Tipask问答网站
  • 6、将机器人移动到指定关节角度或位置
  • IDEA:配置Serializable class without ‘serialVersionUID’ 找不到
  • Rust编程语言代码详细运行、编译方法
  • 日程公布 | 西部地区生活物资保供与城郊大仓基地高质量建设运营论坛(12月5日·西安)
  • 一文了解TensorFlow是什么
  • Docker:清理Docker占用的磁盘空间
  • PostgreSQL详细安装教程
  • 【ROS2】ROS2 C++版本 与 Python版本比较
  • 数据结构4——栈和队列
  • 救生艇..
  • HOG 算法变形:原理、应用与创新发展
  • 行业分析---2024年蔚来汽车三季度财报及科技日
  • [C#] 对图像进行垂直翻转(FlipY)的跨平台SIMD硬件加速向量算法,兼谈并行处理收益极少的原因