从认证说起
认证就是确认用户的身份
一、双因素认证
认证的难点在于如何证明一个人的身份?
我们将能证明用户身份的特征分为以下三类:
- 秘密信息: 密码、私钥等
- 个人物品:银行卡、手机等
- 生理特征:指纹、faceId,虹膜等
这些证据就称为“因素”(factor)。因素越多,证明力就越强,身份就越可靠。
双因素认证就是指,通过认证同时需要两个因素的证据。
比如 去银行柜台取钱就是最常见的双因素认证,用户必须同时提供银行卡和密码,才能取到现金。
二、双因素认证方案
常见的双因素组合是密码+手机,因为手机太普及了,硬件口令令牌都被淘汰了。
比如很多网站登录时,除了输入账号和密码外 还需要输入短消息验证码。
三、OTP的概念
OTP的全称是“基于时间的一次性密码”(One-Time Password)。它是公认的可靠解决方案,已经写入国际标准RFC6238。它有几种实现方式,基于时间是TOTP,基于计数器的是HOTP。 步骤如下:
- 第一步,用户开启双因素认证后,服务器生成一个密钥
- 第二步,服务器提示用户扫描二维码(或者手工输入也行),把密钥保存到用户的手机。也就是说,服务器和用户的手机现在都有了同一把密钥。(密钥必须跟手机绑定。一旦用户更换手机,就必须生成全新的密钥
- 用户登录时,手机客户端使用这个密钥和当前时间戳,生成一个哈希,有效期默认为30秒。用户在有效期内,把这个哈希提交给服务器。
- 第四步,服务器也使用密钥和当前时间戳,生成一个哈希,跟用户提交的哈希对比。只要两者不一致,就拒绝登录。
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);
六、总结
双因素认证的优点在于,比单纯的密码登录安全得多。只要手机上的密钥安全就行。
缺点丰于,登录多了一步,费时且麻烦。
还有一个最大的问题,就是密钥丢失后如何恢复?