前言
最近上班过程中,谷歌到了一个概念OTP,看到它的全称之后,我发现它是一个我们日常生活中已经离不开的东西:(One-Time-Password),顾名思义,一次性密码。恐怕最常见的可能就是我们动态登录账号的时候选择短信(SSM)验证账号。这个验证码就属于OTP。虽然目前工作中没有用到OTP,但是我还是决定了解一下这个概念,说不定以后什么时候就用到了。
OTP概念
首先我们要知道的是,OTP是MFA(Multi-Factor-Authentication)的一个实现模型。先简单了解一下MFA的概念,MFA概括来说就是用户需要提供2种或2种以上的凭证去验证真实身份。相比仅凭账号密码,MFA提供了更加安全的资源保护。下面列举一些MFA的例子:
- 用户安全问题
- 密码
- OTPs
- 软件安全码、keys
- 指纹、人脸识别、声纹及其他生物特征
- 用户行为足迹分析
其实把资源安全的责任附加到用户身上(如上述的1、2等),并不是十分可靠,用户承担了记忆的责任。因此我认为OTP是一个比较负责任的MFA做法。
接下来介绍OTP。
OTP是应用主、网站提供给用户,需要用户接收并且输入的一次性密码,一旦用户使用过之后,再次登录应用或者页面则需要接受另一个新的密码。OTP的形式可以为短信、手机应用等等。
生成OTPs的标准算法有很多,例如SHA-1,它们都有一个共同点:有一个seed和一个moving factor,seed是绑定在账户上的静态值,从账户创建开始即保持不变,moving factor则在每一次OTP请求中改变。根据这个动态因子的不同,OTP又被分为两种:HOTP和TOTP
HOTP
HOTP中,H指代 Hash-based Message Authentication Code,另一种不那么专业的解释方法为:HOTP是一种事件驱动的OTP,它的moving factor基于一个计数器。
HOTP每一次请求,都会让计数器递增,而生成的OTP在下一次计数前保持有效。OTP生成器和服务器会同步监测到用户的有效访问行为。HOTP的一个例子为Yubikey。
TOTP
TOTP中,T指代Time-based,类似HOTP,但是它的moving factor基于时间。
TOTP的有效时间被称为timestep(时间步长),通常被设定为30、60秒,如果在这个时间窗口内没有验证此密码,则会失效。
对比
由于TOTP存在时间窗口,所以在TOTP的运行时间(服务器生成到用户输入这段时间)可能会导致用户使用了无效的TOTP,造成一定的困扰。
而HOTP不存在时间窗口,所以在UE(user experience)上有一定的优势,另一方面,由于较长的输入验证时间,有一定几率被恶意破解。因此,在一些HOTP中,也加入了基于时间的组件,从而二者合一,模糊了TOTP、HOTP之间的界限。
一言
不管是使用HOTP还是TOTP,仅凭账号密码就可以轻松获取资源的时代已经一去不复返,各种行为验证、OTPs的验证正在铺天盖地般普及,了解了OTP的概念、作用,在我们真正要用到的时候,才能知己知彼,选择一个最适合应用、网站的验证方法。
Node.js上otp包的轻度体验。
npm上有许多有关otp的包,下面我来轻度体验其中几个。
notp
- Weekly Downloads:26,565
- Last publish: 6 yrs ago
这个包比较老,但是直到今天还是很多人依赖,它支持Google Authenticator(GA在各个移动端平台上都有免费应用,所以这类包都会说明支不支持)。
体验sample:
var notp = require("notp")
t2 = require('thirty-two')
K = '12345678901234567890'
const token = notp.totp.gen(K, {})
notp.totp.verify(token,K,{})
输出:
Object {delta: 0}
// 返回一个带数据倾斜属性的对象代表验证成功,返回null代表验证失败
otplib
- Weekly Downloads:66,389
- Last publish: a yr ago
这是一个比较全的包,文档内容也很丰富,可以看到周下载次数也很多。
var otplib = require("otplib")
const secret = 'KVKFKRCPNZQUYMLXOVYDSQKJKZDTSRLD';
const tokenT = otplib.totp.generate(secret);
const isValidT = otplib.totp.check(tokenT, secret);
输出:
true
var otplib = require("otplib")
const secret = 'KVKFKRCPNZQUYMLXOVYDSQKJKZDTSRLD';
var counter = '1234567890'
const tokenH = otplib.hotp.generate(secret, counter);
const isValidH = otplib.hotp.check(tokenH, secret, counter);
输出:
true
可以看到node库里已经有很多各种各样的otp包,它们的generator各异,几乎都包揽TOTP、HOTP方式,且支持GA,我们要做的就是依赖之后,在我们的服务器上保存Seed(Key)和Counter或者用TOTP进行验证即可。