两步验证是一些云服务或者游戏中使用得比较广泛的认证方式,用户在登录过程中,除了输入设置的固定密码之外,还需要输入由验证器生成的一次性密码。这种验证很多是基于TOTP(Time-Based One-Time Password,基于时间的一次性口令),它是对OTP(One-Time Password,一次性密码)的拓展,叫作HTOP(HMAC-based One-Time Password,基于HMAC的一次性口令)算法。其基本原理是:
客户端和服务器事先协商好一个密钥K,用于一次性密码的生成过程。此外,客户端和服务器各有一个计数器C,并且事先将计数值同步。
进行验证时,客户端对密钥和计数器的组合(K, C)使用HMAC(Hash-based Message Authentication Code)算法计算一次性密码,如果匹配则验证通过。
具体的实现细节可以参考相关文档:
RFC 4226 - HOTP: An HMAC-Based One-Time Password Algorithm
RFC 6238 - TOTP: Time-Based One-Time Password Algorithm
最近我才在Dropbox上开启了两步验证服务,那就以这个为例看看实际的操作过程。
服务端首先会生成密钥(一组base32字符串)并保存在其数据库与账户对应,然后会用二维码的形式呈现或直接显示出来由用户手动输入。
用户要做的就是将该密钥扫描或输入进验证器(各移动平台都有该应用)中,然后即可看到随时间切换的一次性密码。
点击下一步,系统会要求验证一次当前的密码已保证正确,确认后即可配置成功。
这样在以后登录Dropbox的时候,必须通过两步验证才能完成登录:
因为这里的密钥只有用户和服务器知道,因此相对来说还是比较安全。
对于我们自己来说,只要了解算法和原理,要想实现该功能也很容易。这是Python实现HOTP的一个示例(来源于网络):
def hotp(self, counter): basedSecret = base64.b32decode(self.secret, True) structSecret = struct.pack(">Q", counter) hmacSecret = hmac.new(basedSecret, structSecret, hashlib.sha1).digest() ordSecret = ord(hmacSecret[19]) & 15 tokenSecret = (struct.unpack(">I", hmacSecret[ordSecret:ordSecret+4])[0] & 0x7fffffff) % 1000000 return tokenSecret
而TOTP的代码如下:
def totp(self, period=30): """Generate a TOTP code. A TOTP code is an extension of HOTP algorithm. :param period: A period that a TOTP code is valid in seconds """ # https://tools.ietf.org/html/rfc6238 counter = int(time.time()) // period return self.hotp(counter + offset)
既然是基于时间的一次性密码,也就意味着要求用户时间和服务器时间同步。但这并不能保证,如果用户的设备时间有偏差不就悲剧了么?这时我们需要考虑一点容错,即允许一个时间范围内的密码都有效。以此修改totp函数如下:
def totp(self, period=30): counter = int(time.time()) // period valid_codes = [] for offset in [-2, -1, 0, 1, 2]: valid_codes.append(self.hotp(counter + offset)) return valid_codes
这样返回一组有效的密码,只要与其中一个匹配即算有效。