TOTP 算法的基本原理和实现

一、QQ令牌

QQ 令牌是腾讯公司专为保护 QQ 帐号及游戏帐号安全的密保产品。QQ 令牌于 2010 年 7 月 6 日正式发布。无需联网,每隔 60 秒自动更新的动态密码是这款密保产品最大的特点,在登录、支付、游戏装备转让等敏感操作需要验证身份时,用户可以自主选择是否开启 QQ 令牌验证。

QQ 令牌有手机 APP 和硬件实体两种。

QQ 令牌现已更名为 QQ 安全中心,动态密码更新间隔下调至 30 秒。

1.1、手机 APP

手机 APP:

/post_images/image-20211205123328343.png

1.2、硬件

QQ令牌 1代:

/post_images/image-20210913165747456.jpg

QQ令牌 2代:

/post_images/image-20210913165713456.jpg

那么 QQ 令牌为什么无需联网,就能每隔 30 秒自动更新动态密码?答案是 TOTP 算法,下面我们简单介绍一下它的基本原理和实现。

二、TOTP 算法

TOTP 的全称是"基于时间的一次性密码"(Time-based One-time Password)。它是公认的可靠解决方案,已经写入国际标准 RFC6238

通常情况下,客户端在首次配置完成后,就不再需要与服务端通讯了。借助 TOTP 算法,客户端可以在离线环境下生成 30 秒有效期的 6 位数字验证码。因为不需要联网就能生成短时效的动态密码,所以安全性比较高,很多网站和应用都采用这种形式做两步验证(2FA)。

2.1、如何离线生成一个 30 秒有效期的 6 位数字验证码?

那么手机客户端服务器是如何保证在 30 秒期间都得到同一个 6 位数字验证码(哈希)呢?答案就是下面的公式。

TC = floor((unixtime(now) − unixtime(T0)) / TS)

上面的公式中,TC 表示一个时间计数器,floor 是向下取整函数,unixtime(now)是当前 Unix 时间戳,unixtime(T0)是约定的起始时间点的时间戳,默认是0,也就是1970年1月1日。TS 则是哈希有效期的时间长度,默认是30秒。因此,上面的公式就变成下面的形式。

TC = floor(unixtime(now) / 30)

所以,只要在 30 秒以内,TC 的值都是一样的。前提是服务器和手机的时间必须同步。接下来,就可以算出哈希了。

TOTP = HASH(SecretKey, TC)

上面代码中,HASH就是约定的哈希函数,默认是 SHA-1。

2.2、TOTP 的 JavaScript 实现

TOTP 很容易写,各个语言都有实现。下面我用 JavaScript 实现2fa来演示一下真实代码。

首先,安装这个模块。

npm install --save 2fa

然后,生成一个32位字符的密钥。

var tfa = require('2fa');

tfa.generateKey(32, function(err, key) {
  console.log(key);
});
// b5jjo0cz87d66mhwa9azplhxiao18zlx

现在就可以生成哈希了。

var tc = Math.floor(Date.now() / 1000 / 30);
var totp = tfa.generateCode(key, tc);
console.log(totp); // 683464