BLOG
Enjoy when you can, and endure when you must.
FEB 27, 2014/Python
两步验证的使用和TOTP在Python中的基本实现

两步验证是一些云服务或者游戏中使用得比较广泛的认证方式,用户在登录过程中,除了输入设置的固定密码之外,还需要输入由验证器生成的一次性密码。这种验证很多是基于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

这样返回一组有效的密码,只要与其中一个匹配即算有效。

COMMENTS
LEAVE COMMNT