但是,短消息是不安全的,容易被拦截和伪造,SIM 卡也可以克隆。已经有 案例 ,先伪造身份证,再申请一模一样的手机号码,把钱转走。

因此,安全的双因素认证不是密码 + 短消息,而是下面要介绍的 TOTP

三、TOTP 的概念

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

它的步骤如下。

第一步,用户开启双因素认证后,服务器生成一个密钥。

第二步:服务器提示用户扫描二维码(或者使用其他方式),把密钥保存到用户的手机。也就是说,服务器和用户的手机,现在都有了同一把密钥。

注意,密钥必须跟手机绑定。一旦用户更换手机,就必须生成全新的密钥。

第三步,用户登录时,手机客户端使用这个密钥和当前时间戳,生成一个哈希,有效期默认为30秒。用户在有效期内,把这个哈希提交给服务器。

第四步,服务器也使用密钥和当前时间戳,生成一个哈希,跟用户提交的哈希比对。只要两者不一致,就拒绝登录。

五、TOTP 的算法

仔细看上面的步骤,你可能会有一个问题:手机客户端和服务器,如何保证30秒期间都得到同一个哈希呢?

答案就是下面的公式。

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

上面的公式中,TC 表示一个时间计数器, 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。

TOTP 有硬件生成器和软件生成器之分,都是采用上面的算法。

(说明:TOTP 硬件生成器)

(说明:Google Authenticator 是一个生成 TOTP 的手机 App)

五、TOTP 的实现

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

双因素认证的优点在于,比单纯的密码登录安全得多。就算密码泄露,只要手机还在,账户就是安全的。各种密码破解方法,都对双因素认证无效。

缺点在于,登录多了一步,费时且麻烦,用户会感到不耐烦。而且,它也不意味着账户的绝对安全,入侵者依然可以通过盗取 cookie 或 token,劫持整个对话(session)。

双因素认证还有一个最大的问题,那就是帐户的恢复。

一旦忘记密码或者遗失手机,想要恢复登录,势必就要绕过双因素认证,这就形成了一个安全漏洞。除非准备两套双因素认证,一套用来登录,另一套用来恢复账户。

七、参考链接

  • Multi-factor authentication , by Wikipedia
  • Time-based One-time Password Algorithm , by Wikipedia
  • Enabling Two-Factor Authentication For Your Web Application , by Bozhidar Bozhanov
  • simontabor/2fa , by Simon Tabor
  • (正文完)

    ====================================

    从业两三年后,程序员往往遇到职业瓶颈,80%的人都把时间耗费在熬夜加班、修 Bug,只有少数人选择业余时间精进技术,提升自己的潜力,突破薪资天花板。

    优达学城 (Udacity)作为来自硅谷的前沿技术学习平台,帮你掌握前沿技术。

    它的课程和项目,来自Google、Facebook等硅谷名企,并提供人工审阅、一对一在线答疑等服务,拒绝浪费时间走弯路。

    今年双十一,与其囤积一年都用不完的便宜货,不如来优达学城投资未来提升自我。11月1日~11月11日,课程全场最高减¥1111,让你轻松享有硅谷学习资源!

    优惠席位有限,先到先得,点击 这里 了解详情。

    你没看懂。
    举个例子,客户端算tc的时候使用的当前时间是0点0分0秒,算出一个tc值,服务器端在0点0分到0点0分30秒内算出来的tc都会和客户端一样。超出30s后算出来就不一样了,也就是过期了,验证就失败了。
    你那29,31是个啥,那儿是时间戳,远大于30的值

    实际是服务器会计算当前的和之前几个时间窗口里的验证码,只要你输入的符合其中任何一个就认为你通过了.
    支持这种协议的应用不只有google的验证器,(如微软的,或者authy) 而且计算验证码不需要网络,所以也不存在被墙的概念.
    小黄车四位电子密码锁, 银行的电子密令牌(自动生成6位验证码的那种)应该都是类似的方法.

    本质上还是通过一个密码和当前时间来生成动态验证码,所以那个密码丢失也就没有安全性可言.不过使用过程中,并不会直接键入这个密码,通过生成的验证码和时间也不能反推原密码,所以还是很安全的.
    两步验证的安全性并不是绝对的,只是多一步验证,假设该步验证丢失的概率1/n,那么可以认为安全性提高了n倍.

    你没看懂。
    举个例子,客户端算tc的时候使用的当前时间是0点0分0秒,算出一个tc值,服务器端在0点0分到0点0分30秒内算出来的tc都会和客户端一样。超出30s后算出来就不一样了,也就是过期了,验证就失败了。
    你那29,31是个啥,那儿是时间戳,远大于30的值

    你理解错了,并非是说 (serverTime-30, serverTime+30)这个区间内算出的密钥都一致。
    这个30是一个模值,可以理解为 时间戳%30 一致的话,算出来的密钥都是一致的。
    totp客户端一般都会有一个刷新时间,每30秒会刷新一次密钥,这个刷新的时刻恰好是 时间戳%30 == 0 的时刻。
    当然,30作为步长,也是可以配置的。