Windless
订阅/Feed
稗田千秋(i@wind.moe)

实现简单的OTP Generator

稗田千秋
Nov.15 2016 code

之前曾尝试给博客添加了一个 OTP 认证,不过是引用某第三方认证 API,经常会导致请求超时等不可料错误,于是就尝试寻找着在后端本地实现的方法,发现了 PyOTP 这个库,在浅读了源码以及相关文档之后初步了解了 OTP 实现的原理,所以记下来备份。

OTP

OTP(One Time Password,动态密码),一般用来实现双因子认证,通过特殊的构造和哈希,实现了“动态”的特性,可以有效防止重放攻击(Replay Attacks),大大增加暴力破解成本(虽然在被爆破的时候小站服务器肯定先挂了XD

主要的验证方式有两种,HOTP(HMAC-Based One-Time Password)和 TOTP(Time-Based One-Time Password),像常用的 Google authenticator,开启二步验证之后会给予一个二维码,用对应 APP 扫描一下即可得到六位验证码,当然此时若是使用二维码识别工具的话也能发现这个二维码其实就是一段 URI Scheme,诸如 otpauth://totp/Windless:i%40wind.moe?issuer=chiaki&secret=secretkey&algorithm=SHA256

注意到其中传递了密钥 secret 以及摘要算法类型,这样只要客户端与服务端的实现是一致的,就能保证在同一时间得出的 OTP 是一致的。

TOTP

在 RFC 6328 的文档里是这么介绍 TOTP 的:

an extension of the One-Time Password (OTP) algorithm, namely the HMAC-based One-Time Password (HOTP) algorithm, as defined in RFC 4226, to support the time-based moving factor.

大致是说 TOTP 就是基于时间因子的 HOTP 算法变种,一般定义

$$ TOTP = HOTP(K, T) $$

K 是服务端和客户端协商好的密钥, T 的值则是由

$$ T = \frac{(T_N - T_0)}{X} $$

来确定。时间差除以步长得到的结果,某种意义上说也是采用计数器形式的 HTOP。其中Tn 时当前时间戳,T0 是开始计算的时间,缺省值为0,X 是以秒为单位的步长,通常默认值是 X = 30 ,后两个值约定后就不应该再更改。

Python 代码实现

class TOTP:
    def __init__(self, secret, step=30):
        self.secret = bytearray(secret, encoding='utf-8')
        self.step = step
        self.digest = hashlib.sha256

    def generate(self, timestamp):
        hasher = hmac.new(self.secret, bytearray(timestamp), self.digest)
        hmac_hash = bytearray(hasher.digest())
        offset = hmac_hash[-1] & 0xf
        code = ((hmac_hash[offset] & 0x7f) << 24 |
                (hmac_hash[offset + 1] & 0xff) << 16 |
                (hmac_hash[offset + 2] & 0xff) << 8 |
                (hmac_hash[offset + 3] & 0xff))
        return '%06d' % (code % 10 ** 6)

    def now(self):
        return self.generate(int(time.mktime(datetime.datetime.now().timetuple()) / self.step))

    def verify(self, pwd):
        return str(pwd) == str(self.now())

这里没有采用 Google 开源的实现,而是采用最简单的方式来展示如何实现 TOTP。

一般情况下我们只需要传入双方约定好的密钥即可生成一个 OTP,具体算法请参考 RFC 文档,需要在客户端和服务端口实现同样的生成算法,然后将客户端生成的动态密码传入服务器进行验证,即可得出所需结果。

引用

--END--
文章创建于 2016-11-15 23:56:37,最后更新 2016-11-15 23:56:37
Comment
尝试加载Disqus评论, 失败则会使用基础模式.
    • play_arrow

    About this site

    version:1.02 Alpha
    博客主题: Lime
    联系方式: i@wind.moe
    写作语言: zh_CN & en_US
    博客遵循 CC BY-NC-SA 4.0许可进行创作

    此外,本博客会基于访客的Request Headers记录部分匿名数据用于统计(Logger的源码见Github),包含Referer, User-Agent & IP Address.个人绝不会主动将数据泄露给第三方