【TOTP】基于时间的动态密码及其工程实践

news2025/1/13 13:33:18

探究了常见的动态密码的实现方式及其底层原理,并基于java做出了工程实践。

文章目录

  • A.来源于一个现象的好奇
  • B.2FA
  • C.TOTP
    • 1.什么是TOTP
    • 2.原理详解(基于java-totp项目分析)
    • 3.这样真的安全吗
    • 4.常见的支持TOTP的软件
      • 1.Google Authenticator
      • 2.Microsoft Authenticator
  • D.项目实践(基于Java)
  • E.总结
  • 参考资料


A.来源于一个现象的好奇

用过公司vpn的都知道,不管是阿里郎还是字节那个连vpn的工具(叫啥记不得了),在电脑连上的时候,都需要手机端做一个二次校验,输入六位数字,这个六位数字每隔几十秒会变动一下。
对于这个东东,有时候好奇心驱使,会去想,这是怎么实现的呢?
最开始我想的一个方案:

  • 客户端维护一个定时器,每隔几十秒去请求一个获取验证码的接口,每次请求的时候,里面存储的验证码会被刷新。

这种想法很合理(起初),逐渐往深处想,这个二次验证(2FA)是干嘛用的呢?

  • 当我们的办公电脑不在内网环境的时候,需要通过这个vpn软件连接内网,当连接上内网后一段时间不使用,会自动注销。
  • 这个连接vpn的软件默认是登录了我们的公司账号的。
  • 如果其他人在我们电脑打开的状态下接触到了我们的电脑,那么会因为无法输入验证码而无法进入公司内网。

这个二次验证的功能就是这样没错了,直到有一天,意外出现了:

  • 手机打开飞行模式忘关了,然后我打开阿里郎,像往常一样在电脑上输入阿里郎的动态验证码,连接内网。
  • 奇怪的事情发生了,竟然连上了!!!
  • 这样我们上面的假想就推翻了,因为上面假想的是要去请求特定的接口来刷新验证码,按照这个逻辑,验证码肯定是服务端生成的。但是现在没网的状态下,也能正常登陆上,说明这个验证码的生成逻辑在客户端!!!

是怎样实现,服务端和客户端不进行通信的情况下,也能做二次验证的,这点引起了我的好奇心,于是开始了查找资料的过程, 就有了后面的梳理和实现。

说到这里,突然想起还有一个现象,就是在大概七八年前,银行卡进行支付的时候,有时候需要输入一个动态密码卡里面的动态密码才能正常支付,这个卡很明显是没联网的,但是也能实现安全的验证,现在想来和上面这个现象应该是一样的道理。

B.2FA

首先我们要了解一下什么是2FA,2FA的全称是双重身份验证,是一种为帐户增加额外安全性的方法。往往第一个因素是账户的密码,而第二个因素区别于传统的密码验证,由于传统的密码验证是由一组静态信息组成,如:字符、图像、手势等,很容易被获取,相对不安全。2FA是基于时间、历史长度、实物(信用卡、SMS手机、令牌、指纹)等自然变量结合一定的加密算法组合出一组动态密码,一般每60秒刷新一次。不容易被获取和破解,相对安全。(取自百度百科)

总结一下,2FA就是在静态密码验证的基础上,再结合一定的自然变量组合出动态密码进行二次验证。

很明显,上述这个现象就是一种2FA的实现。而2FA 系统的其他名称包括OTP(一次性密码)和TOTP(基于时间的一次性密码算法)。
我们这里详细的了解一下TOTP。

C.TOTP

1.什么是TOTP

TOTP(Time-Based One-Time Password Algorithm),是一种基于时间的一次性密码算法,算法的详细说明见:https://www.rfc-editor.org/rfc/rfc6238,其公式表示如下:

// K 代表我们在认证服务器端以及密码生成端(客户设备)之间共享的密钥
// C 表示事件计数的值,8 字节的整数,称为移动因子(moving factor)
// HMAC-SHA-1 表示对共享密钥以及移动因子进行 HMAC 的 SHA1 算法加密,得到 160 位长度(20字节)的哈希结果
// Truncate 表示截断函数 将得到的哈希值进行阶段
// digit 指定动态密码长度,比如我们常见的都是 6 位长度的动态密码
HOTP(K,C) = Truncate(HMAC-SHA-1(K,C))
PWD(K,C,digit) = HOTP(K,C) mod 10^Digit

TOTP = HOTP(K, T) // T is an integer and represents the number of time steps between the initial counter time T0 and the current Unix time

More specifically, T = (Current Unix time - T0) / X, where the
default floor function is used in the computation.

TOTP算法实际上是基于HOTP(An HMAC-Based One-Time Password Algorithm)(基于事件计数的一次性密码生成算法),只不过把事件计数变成了时间计数。HOTP算法的详细说明见:https://www.rfc-editor.org/rfc/rfc4226。

2.原理详解(基于java-totp项目分析)

从上面的公式大概可以看出,是这么一个流程:

  • 生成一个随机的密钥。
  • 基于时间取一个计数值。通常的做法是用当前的时间除以动态密码的有效时间。
  • 然后将这个计数值和密钥进行哈希运算,并截断到指定的长度。
  • 最后对数位的最大值取余。

有一个java库实现了TOTP算法,我们可以详细分析一下实现的原理: https://github.com/samdjstevens/java-totp。

使用java-totp总共分为两个部分:

  • 1.生成密钥。
  • 2.验证动态密码。

密钥生成部分代码如下:

在这里插入图片描述
可以看到随机生成了20个字节并且用base32进行编码。

动态密码验证部分的代码如下:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
大概分为以下几个部分:

  • 1.先取出当前时间所在的桶(30秒一个桶)。
  • 2.在允许的时间误差范围内进行验证。
  • 3.验证的过程先用时间的桶和密钥进行一个哈希运算,然后取低32位(一个整数),最后对10^6取余。

可以看出来,这个实现完美遵循了上面算法的标准,原理也很好理解了。

3.这样真的安全吗

理解了这个算法后,还是有点疑惑,这个算法真的安全吗?

虽然说一个桶内生成的动态密码一定是一样的,但是不同的桶生成的密码一定是不一样的吗?这可不一定,注意到哈希后进行了低32位的截断操作,并且还有对数位的取余操作,其实这两个操作大大提升了密码碰撞的概率。根据生日攻击,是有可能被碰撞出来的。

但是,我们追求的是计算上的安全,在短短的几十秒内碰撞出来的概率,实在是太小了,并且实际应用的时候,如果连续输错几次,可以结合图形验证码进行进一步的验证。

所以,这样的算法在计算上,是非常安全的。

4.常见的支持TOTP的软件

1.Google Authenticator

Google Authenticator毫无疑问是最受欢迎的2FA软件,简洁轻便,无需登录google账号。缺点是数据都存储在本地,换手机的话需要导出数据。

2.Microsoft Authenticator

Microsoft Authenticator 支持工作、学校和非 Microsoft 帐户的多重身份验证。输入密码后,应用提供第二层安全保护。登录时,你将输入密码,然后系统将要求你再用一种方式来证明是你本人。请批准发送至 Microsoft Authenticator 的通知,或输入应用生成的验证码。

https://baike.baidu.com/item/Microsoft%20Authenticator/58322728?fr=aladdin

D.项目实践(基于Java)

我们可以简单的基于java-totp库实现一个用于2FA的验证service。(底层实现这种就没必要重复造轮子了)
先导包:

 <dependency>
 	<groupId>dev.samstevens.totp</groupId>
        <artifactId>totp-spring-boot-starter</artifactId>
     <version>1.7.1</version>
  </dependency>

上代码:


import dev.samstevens.totp.code.CodeVerifier;
import dev.samstevens.totp.code.DefaultCodeGenerator;
import dev.samstevens.totp.code.DefaultCodeVerifier;
import dev.samstevens.totp.code.HashingAlgorithm;
import dev.samstevens.totp.exceptions.QrGenerationException;
import dev.samstevens.totp.qr.QrData;
import dev.samstevens.totp.qr.QrDataFactory;
import dev.samstevens.totp.qr.QrGenerator;
import dev.samstevens.totp.qr.ZxingPngQrGenerator;
import dev.samstevens.totp.secret.DefaultSecretGenerator;
import dev.samstevens.totp.secret.SecretGenerator;
import dev.samstevens.totp.time.SystemTimeProvider;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;


import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Scanner;

import static dev.samstevens.totp.util.Utils.getDataUriForImage;

@Service
@Slf4j
public class TOTPVerifyService {

    private final SecretGenerator secretGenerator = new DefaultSecretGenerator();
    private final QrDataFactory qrDataFactory = new QrDataFactory(HashingAlgorithm.SHA1, 6, 30);
    private final QrGenerator qrGenerator = new ZxingPngQrGenerator();
    private final CodeVerifier verifier = new DefaultCodeVerifier(new DefaultCodeGenerator(HashingAlgorithm.SHA1, 6), new SystemTimeProvider());
    private final Map<String, String> uidAndSecret = new HashMap<>();

    public Map<String, String> setupDevice() throws QrGenerationException {
        // 生成 TOTP 密钥
        String secret = secretGenerator.generate();
        QrData data = qrDataFactory.newBuilder().secret(secret).issuer("ATFWUS-TEST").build();

        // 将生成的 TOTP 密钥转换为 Base64 图像字符串
        String qrCodeImage = getDataUriForImage(
                qrGenerator.generate(data),
                qrGenerator.getImageMimeType());

        System.out.println(secret);

        // 返回密钥 和 密钥二维码
        Map<String, String> result = new HashMap<>(2);
        result.put("secret", secret);
        result.put("qrCodeImage", qrCodeImage);
        return result;
    }

    public boolean deviceVerify(String uid, String secret, String code) {
        if (verifier.isValidCode(secret, code)) {
            // 将uid绑定secret并存储
            uidAndSecret.put(uid, secret);
            return true;
        } else {
            return false;
        }
    }

    public void cancelDeviceVerify(String uid) {
        // 取消绑定TOTP密钥
        uidAndSecret.remove(uid);
    }

    public boolean checkCode(String uid, String verifyCode) {
        // 用uid取出绑定的secret
        if(!uidAndSecret.containsKey(uid)) {
            return false;
        }
        String secret = uidAndSecret.get(uid);
        return verifier.isValidCode(secret, verifyCode);
    }

    // test
    public static void main(String[] args) throws Exception {
        Scanner sc = new Scanner(System.in);
        TOTPVerifyService totpVerifyService = new TOTPVerifyService();
        totpVerifyService.setupDevice();
        String uid = "atfwus";
        System.out.println("bind your device!");
        while(true) {
            System.out.println("input secret: ");
            String secret = sc.nextLine();

            System.out.println("input code: ");
            String code = sc.nextLine();

            if(totpVerifyService.deviceVerify(uid, secret, code)) {
                break;
            }
            System.out.println("try again!!!");
        }
        System.out.println("bind device successful!!!");
        System.out.println();
        System.out.println("2FA check!!!");
        while(true) {
            System.out.println("input secret: ");
            String secret = sc.nextLine();

            System.out.println("input code: ");
            String code = sc.nextLine();

            if(totpVerifyService.checkCode(uid, code)) {
                break;
            }
            System.out.println("try again!!!");
        }
        System.out.println("check code pass!!!");
    }
}

将生成的secret复制到手机上,或者扫描生成的二维码即可完成验证。
请添加图片描述

E.总结

连公司vpn的那个软件其实就是通过TOTP的方式进行的验证(也有可能基于其它计数机制,但总的原理不变),这样客户端无需和服务端进行通信,也能实现安全的验证。现实生活中还能找到很多类似的应用例子:像银行的动态密码卡就是一个实例。


参考资料

  • https://www.rfc-editor.org/rfc/rfc6238 TOTP算法详细介绍
  • https://www.rfc-editor.org/rfc/rfc4226 HOTP算法详细介绍
  • https://github.com/samdjstevens/java-totp 一个基于TOTP实现的java库
  • https://baike.baidu.com/item/2FA/14695073?fr=aladdin 2FA介绍
  • https://segmentfault.com/a/1190000008394200 动态密码算法介绍

ATFWUS 2022-12-07

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/70497.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

RCNN学习笔记-MobileNet3

更新Block(bneck倒残差结构) 1.加入SE&#xff08;自注意力模块squeeze-and-excite bottleneck&#xff09;模块。当stride1&#xff08;高和宽是不会变化的&#xff09;且inputc outputc才有shortcut连接。 相反&#xff0c;我们将它们全部替换为扩展层中通道数量的1/4。我…

功率放大器输出阻抗的影响因素有哪些原因

关于功率放大器的疑问有很多&#xff0c;前阵子有人咨询影响功率放大器输出阻抗的有哪些因素&#xff0c;今天就请安泰电子来为大家解释&#xff0c;同时再为大家科普一下功率放大器的知识。 图&#xff1a;信号源和负载的放大器的简化模型 在搞清楚影响功率放大器输出阻抗因素…

速锐得解码本田雅阁混动版整车网关CAN总线通信协议DBC控制策略

本田汽车增城工厂就在附近50多公里的地方&#xff0c;和比亚迪汽车差不多&#xff0c;无论怎么跑都得1个多小时&#xff0c;也因为近水楼台的天然优势&#xff0c;而我们也与本田安全驾驶中心有多次深度的合作。碗里的肉&#xff0c;基本上都是上过了速锐得砧板。 近&#xff0…

m基于FPGA的半带滤波器verilog设计,对比普通结构以及乘法器复用结构

目录 1.算法描述 2.仿真效果预览 3.verilog核心程序 4.完整FPGA 1.算法描述 HBF模块由半带滤波器&#xff08;HBF&#xff09;和抽取模块组成。该模块的任务是实现2倍抽取进一步降低信号采样速率。由于HBF的冲激响应h(k)除零点外其余偶数点均为零&#xff0c;所以用HBF实现…

5G+无人驾驶融合创新,赋能港口智能化发展!

导语 | 在新一轮科技革命的时代背景下&#xff0c;5G 技术和无人驾驶的创新融合&#xff0c;使得我国当前港口的智慧化建设走在了世界的前列&#xff0c;智慧港口的发展不断深入。此次&#xff0c;我们邀请到了飞步科技的联合创始人兼 CTO、腾讯云 TVP 杨政老师&#xff0c;他将…

【LeetCode】No.116. Populating Next Right Pointers in Each Node -- Java Version

题目链接&#xff1a;https://leetcode.com/problems/populating-next-right-pointers-in-each-node/description/ 1. 题目介绍&#xff08;&#xff09; You are given a perfect binary tree where all leaves are on the same level, and every parent has two children. T…

安卓APP源码和设计报告——小说阅读器

班级 姓名 学号 答辩情况 考核项满分成绩得分掌握计算机系统软硬件资源管理的原理&#xff0c;能够设计针对计算机领域复杂工程问题的解决方案&#xff0c;设计满足特定需求的软硬件系统&#xff0c;并具有对解决方案在特定约束条件下进行工程设计和开发的能力。30能够针对计…

Excel 函数大全之 INTERCEPT function 获取线性回归线的截距

描述 使用现有的 x 值和 y 值计算直线与 y 轴相交的点。截点基于通过已知 x 值和已知 y 值绘制的最佳拟合回归线。当您想要在自变量为 0(零)时确定因变量的值时,请使用 INTERCEPT 函数。例如,当您的数据点是在室温或更高温度下获取的时,您可以使用 INTERCEPT 函数预测金属…

BIGEMAP APP导入/导出文件\照片(kml\shp\cad(dxf)\txt\excel)

APP数据导入&#xff1a; 1、kml\bmv文件通过QQ、微信等发送到手机端&#xff0c;在手机端下载文件&#xff0c;然后选择其他应用打开&#xff0c;选择bigemap打开就可以了。 2、其他数据导入&#xff08;其他数据包括&#xff1a;shp、kml\kmz、CAD的dxf、txt、excel、csv等…

java通过idea进行远程调试

1&#xff0c;基于SpringBoot使用IDEA工具 在pom.xml中配置 里配置jvmArguments参数 -Xdebug -Xrunjdwp:transportdt_socket,address8008,servery,suspendn&#xff1a; <build><plugins><plugin><groupId>org.springframework.boot</groupId>…

MySQL是怎样加锁的

是不是很多人都对 MySQL 加行级锁的规则搞的迷迷糊糊&#xff0c;对记录一会加的是 next-key 锁&#xff0c;一会加是间隙锁&#xff0c;一会又是记录锁。这次就带大家浅浅地聊一下MySQL是怎样加锁的。 什么 SQL 语句会加行级锁&#xff1f; InnoDB 引擎是支持行级锁的&#…

第十二章 Golang家庭收支记账软件项目

1.项目开发流程 2.项目需求说明 模拟实现基于文本界面的《家庭记账软件》该软件能够记录家庭的收入&#xff0c;支出&#xff0c;并能够打印收支明细表 3.项目的界面 4.项目代码实现 实现基本功能&#xff08;先使用面向过程&#xff0c;后面改成面向对象&#xff09; 功能1…

【Shell 脚本速成】10、Shell 流程控制 while 循环

目录 一、while 介绍 1.1 while 语法 二、while 与 shell 运算 2.1 比较运算 2.2 逻辑运算 2.3 文件类型判断 2.4 特殊条件 三、while 与循环控制语句 3.1 sleep 语句 3.2 break 3.3 continue 四、while 嵌套其他语句 4.1 while 嵌套 if 4.2 while 嵌套 for 4.…

XSKY星晨天合-后台开发岗

一面 TCP与UDP区别UDP的优点&#xff08;实时性好&#xff0c;占用资源低&#xff0c;无需三次握手较少被黑客利用的机会&#xff09;UDP增加哪些功能可以实现更可靠、更稳定、且保证有序&#xff08;超时重传、滑动窗口流量控制、序号/确认序号&#xff0c;面试官提到了kcp&a…

使用HTML制作静态宠物网站——蓝色版爱宠之家(HTML+CSS)

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

【ES6】阮一峰ES6学习(六) Proxy(一)

Proxy1. 前言2. 使用1. get()方法2. set()方法未完待续1. 前言 es6中全新设计了一个叫Proxy的类型&#xff0c;Proxy这个词的原意是代理&#xff0c;用在这里表示由它来”代理“某些操作&#xff0c;可以译为”代理器“&#xff0c;Proxy就是专门为对象设置访问代理器的&#…

【1775. 通过最少操作次数使数组的和相等】

来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 给你两个长度可能不等的整数数组 nums1 和 nums2 。两个数组中的所有值都在 1 到 6 之间&#xff08;包含 1 和 6&#xff09;。 每次操作中&#xff0c;你可以选择 任意 数组中的任意一个整数&…

1978-2021年全国GDP平减指数计算模板

全国GDP平减指数计算公式可以给定基期&#xff0c;自动计算平减指数&#xff01; 1、时间区间&#xff1a;1978-2021年 2、计算说明&#xff1a;GDP平减指数名义GDP/实际GDP 实际GDP又称不变价GDP&#xff0c;名义GDP就是公布的数字&#xff0c;它没有考虑通货膨胀因素。 给…

【Unity】UnityWebRequest学习——Unity中的HTTP网络通信

目录UnityWebRequest 简介HTTP网络通信流程HTTP 三点注意事项HTTP请求HTTP响应例子使用Unity内置的UnityWebRequest类进行HTTP请求&#xff08;GET&#xff09;使用BestHTTP插件进行HTTP请求&#xff08;GET&#xff09;使用Unity内置的UnityWebRequest类进行HTTP请求&#xff…

主成分分析/因子分析与线性映射

数据降维&#xff0c;包括主成分分析PCA和因子分析FA&#xff0c;都离不开特征值和特征向量。今天先不细说特征值和特征向量&#xff0c;先说一说理解数据降维的一个关键概念&#xff0c;线性映射。 看到csdn里很多文章讲特征值与特征向量时&#xff0c;都会先讲讲线性映射&am…