- 参考资料在文末注明,如本文有错漏欢迎评论区指出👏
目前很多应用都逐步采用了双因子认证或者说MFA认证方案,因此本文介绍一下背后的机制和TOTP算法原理。使用TOTP算法,只要满足两个条件:1)基于相同的密钥;2)时钟同步;只需要事先约定好密钥,TOTP算法就可以保证校验段和被校验端具有相同的输出。
OTP
在介绍 TOTP 算法前,先介绍一下 OTP 算法。OTP,One Time Password,又称一次性口令、一次性密码、动态密码、单次有效密码。OTP 基于专门的算法每隔一定的时间间隔生成一个不可预测的随机数字组合。OTP密码有效期仅在一次会话或者交易过程中,因此不容易受到重放攻击。OTP分为计次使用和计时使用两种,计次使用的OTP产出后,可在不限时间内使用。计时使用的 OTP 需要设置密码有效时间,从30秒到两分钟不等,而 OTP 在进行认证之后即废弃不用,下次认证必须使用新的密码,降低了不经授权访问限制资源可能性。
OTP密码进一步分为:
- HOTP:HMAC-based One-Time Password ,基于 HMAC 算法的一次性密码。事件同步,通过某一特定的事件次序及相同的种子值作为输入,通过 HASH 算法运算出一致的密码。
- TOTP:Time-based One-Time Password写,基于时间戳算法的一次性密码。 时间同步,基于客户端的动态口令和动态口令验证服务器的时间比对,一般每 30 秒产生一个新口令,要求客户端和服务器能够十分精确的保持正确的时钟,客户端和服务端基于时间计算的动态口令才能一致。
计算 OTP 串的公式:OTP(K,C) = Truncate(HMAC-SHA-1(K,C))
K 表示秘钥串;C 为随机数;HMAC-SHA-1 表示使用 SHA-1 做 HMAC;Truncate 表示怎么截取加密后的串,并取加密后串的哪些字段组成一个数字;
对 HMAC-SHA-1 方式加密来说,Truncate 实现如下:
HMAC-SHA-1 加密后的长度得到一个 20 字节的密串;取这个 20 字节的密串的最后一个字节,取这字节的低 4 位,作为截取加密串的下标偏移量;按照下标偏移量开始,获取4个字节,按照大端方式(符合人类习惯的形式,比如 0x12 34 , 0x12 存放在低地址,大端即大端在前/高位在低地址存放的意思)组成一个整数;截取这个整数的后 6 位或者 8 位转成字符串返回。
TOTP 将其中的参数 C 变成了由时间戳产生的数字:C = (T - T0) / X; T 表示当前 Unix 时间戳:T0 一般取值为 0,也就是 1970 年 1 月 1 日。X 表示时间步数,也就是说多长时间产生一个动态密码,这个时间间隔就是时间步数 X,系统默认是 30 秒;
一个信息系统要集成 OTP 的认证方式,需要完成以下的工作:
1、开发手机令牌
2、开发认证的服务端,服务端需要完成令牌种子生成、令牌种子分发、动态口令生成等功能。
3、在OTP认证的服务端需要将业务系统的用户与分发的令牌建立关联,这样才能完成最终的登录验证。
TOTP
TOTP 算法(Time-based One-time Password algorithm) 即基于具有时间戳计数器的 OTP。通过定义纪元(T0)的开始并以时间间隔(TI)为单位计数,将当前时间戳变为整数时间计数器(TC)。TOTP 实现可以使用 HMAC-SHA-256 或HMAC-SHA-512 (基于 SHA-256 或 SHA-512)函数来实现,而不是 HOTP 计算中的 HMAC-SHA-1 函数【注:SHA1 存在 HASH 碰撞】。
TOTP 密码生成过程
- 初始化:用户在服务提供商(如 Google 等)注册账户时,服务提供商(服务器)会为用户生成一个 密钥(Secret Key) 并保存在数据中。然后把这个密钥会以某种方式(通常是 二维码 )分享给用户,用户将其添加到(通过扫码)自己的身份验证器应用(如 Google Authenticator、Microsoft Authenticator)中。
- 生成 TOTP:身份验证器应用会按照固定的时间间隔(常见的是 30 秒)使用 HMAC-SHA1 算法,使用当前的时间戳和在初始化步骤中获取的密钥,生成一个新的一次性密码,这个密码通常是一个 6 位数字(但也可能更长或更短)。
- 验证 TOTP:当用户尝试登录或执行需要验证的操作时,会被要求提供当前的一次性密码。用户从自己的身份验证器应用中获取这个密码,并输入到服务提供商的网站或应用中。服务提供商会使用同样的算法和密钥,以及当前的时间戳,生成一个一次性密码,并将其与用户提供的密码进行比较。如果两个密码匹配,那么用户的身份就被认为已经验证。
基于 Node.js 实现支持 Google 身份验证器的 TOTP 算法
import { Secret, TOTP } from 'otpauth';
// 生成密钥
const secret = Secret.fromUTF8('your_secret_key');
// 生成TOTP实例
const totp = new TOTP({
secret,
issuer: 'Example',
label: 'alice@example.com'
});
// 生成token
const token = totp.generate(); // 30s
console.log('totp token:')
console.log(token);
// 验证token
const isValid = totp.validate({
token,
window: 1 // 一个窗口为一个 period
});
if(isValid !== null) {
console.log('Valid token');
} else {
console.log('Invalid token');
}
// 转成uri格式
const uri = totp.toString();
console.log(uri);
Reference
- Google身份验证器、基于时间的一次性密码 (TOTP)算法
- OTP概念及实现原理简析
- 基于时间的一次性密码 TOTP 详解
- 密码学I:密码系统、OTP密码和密码的安全性
- 一文搞懂 OTP 双因素认证
- 使用 Golang 实现基于时间的一次性密码 TOTP
- 基于 Node 实现 TOTP:Otplib