从认证说起

认证就是确认用户的身份

一、双因素认证

认证的难点在于如何证明一个人的身份?

我们将能证明用户身份的特征分为以下三类:

  1. 秘密信息: 密码、私钥等
  2. 个人物品:银行卡、手机等
  3. 生理特征:指纹、faceId,虹膜等

这些证据就称为“因素”(factor)。因素越多,证明力就越强,身份就越可靠。

双因素认证就是指,通过认证同时需要两个因素的证据。

比如 去银行柜台取钱就是最常见的双因素认证,用户必须同时提供银行卡和密码,才能取到现金。

二、双因素认证方案

常见的双因素组合是密码+手机,因为手机太普及了,硬件口令令牌都被淘汰了。

比如很多网站登录时,除了输入账号和密码外 还需要输入短消息验证码。

三、OTP的概念

OTP的全称是“基于时间的一次性密码”(One-Time Password)。它是公认的可靠解决方案,已经写入国际标准RFC6238。它有几种实现方式,基于时间是TOTP,基于计数器的是HOTP。 步骤如下:

  1. 第一步,用户开启双因素认证后,服务器生成一个密钥
  2. 第二步,服务器提示用户扫描二维码(或者手工输入也行),把密钥保存到用户的手机。也就是说,服务器和用户的手机现在都有了同一把密钥。(密钥必须跟手机绑定。一旦用户更换手机,就必须生成全新的密钥
  3. 用户登录时,手机客户端使用这个密钥和当前时间戳,生成一个哈希,有效期默认为30秒。用户在有效期内,把这个哈希提交给服务器。
  4. 第四步,服务器也使用密钥和当前时间戳,生成一个哈希,跟用户提交的哈希对比。只要两者不一致,就拒绝登录。

TOTP和HOTP的算法基本一致,TOTP使用时间因子做为变量,HOTP使用counter来做为变量,下面我们只解释TOTP

四、TOTP算法

你可能有一个问题:客户端和服务器如何保证30秒期间都得到同一个哈希呢?

答案就是下面的公式:

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

上面的公式中,TC表示一个时间计数器,unixtime(now) 是当前unix时间戳,unixtime(T0)是约定的起始时间点的时间戳,默认是0。TS内里是哈希有效期的时间长度,默认是30秒。因此,上面的公式就变成下面的形式。

TC = floor(unixtime(now) / 30)

所以只要在30秒以内,TC的值都是一样的。前提是服务器和手机的时间必须同步。

接下来,就可以算出哈希了。

TOTP = HASH(SecretKey,TC)

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

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

软件:推荐Google Authenticator ,当然也有微信小程序版的

五、TOTP的实现

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

long timestamp = System.currentTimeMillis() / 1000 / 30;
        Mac mac = Mac.getInstance("HMACSHA1");
        mac.init(new SecretKeySpec(Base32String.decode(secretKey), ""));
        byte[] hash = mac.doFinal(ByteBuffer.allocate(8).putLong(timestamp).array());
        int offset = hash[hash.length - 1] & 0xF;
        int truncatedHash = new DataInputStream( new ByteArrayInputStream(
                  hash, offset, hash.length - offset)).readInt() & 0x7FFFFFFF;
        int pinValue = truncatedHash % 1_000_000;
        String code = StringUtils.leftPad(String.valueOf(pinValue), 6, '0');
        System.out.println(code);

六、总结

双因素认证的优点在于,比单纯的密码登录安全得多。只要手机上的密钥安全就行。

缺点丰于,登录多了一步,费时且麻烦。

还有一个最大的问题,就是密钥丢失后如何恢复?