动态口令(OTP,One-Time Password)原理与实践(TOTP)
TOTP:Time-Based One-Time Password Algorithm,基于时间同步的一次性口令,动态口令
技术标准:https://tools.ietf.org/html/rfc6238
参考链接:
https://blog.csdn.net/LVXIANGAN/article/details/73775969
https://www.cnblogs.com/loveyou/p/6989064.html
使用阿里云身份宝(或者Google Authenticator)时间同步实现OTP动态口令
如上图,是一种基于时间同步的OTP计算方式,是通过客户端和服务器持有相同的密钥并基于时间基数,服务端和客户端采用相同的Hash算法,计算出长度为六位的校验码。当客户端和服务端计算出的校验码相同是,那么验证通过。
由于客户端需要存储密钥和计算校验码的载体,阿里云的身份宝(或者Google 的Authenticator)提供了手机端的APP进行密钥存储和校验码计算。下面我们以这两款客户端为例,实现在应用采用OTP进行权限验证,主要流程如下图:
【生成密钥】
String secretBase32 = TotpUtil.getRandomSecretBase32(64);
oper.setOtpSk(secretBase32);
【构造扫码字符串】
格式:otpauth://totp/[客户端显示的账户信息]?secret=[secretBase32]
String totpProtocalString = TotpUtil.generateTotpString(operCode, username, secretBase32);
【生成二维码】
QRUtil.generateMatrixPic(totpProtocalString, 150, 150, filePath, fileName);
【生成动态口令】
secretHex = HexEncoding.encode(Base32String.decode(secretBase32))
long timeStep = 30L; // 动态口令有效性持续的时间区间 30s
steps = Long.toHexString(System.currentTimeMillis() / 1000L / timeStep).toUpperCase();
steps = "000000000000".substring(0, 16-steps.length())+steps;
generateTOTP(secretHex, steps, "6", "HmacSHA1");
=========================================================================================
显然验证的时候要求客户端和服务端时间上基本同步,二者相差应该在 timeStep 以内,否则客户端和服务端计算得到的动态口令就不一致。
这一特性使得用户必须在 timeStep 时间内获取并输入、提交动态口令。从算法看能够成功验证的时间区间在时间轴上是长度为 timeStep 前后排列、紧密连接的时间窗口。一旦用户获取动态口令的时间和服务端验证动态口令的时间不在同一个时间区域内,验证将失败。
如果把时间窗的大小 timeStep 设置大一点,可以降低正常验证中因错开时间窗导致失败的概率。但这又引入另外一个问题:如果用户某次使用的动态口令被泄露,那么在一定时间内被泄露的动态口令还可以成功地被用来做身份验证。
① 为提升用户体验同时保证安全性,此处对验证环节做一些调整:
数据库中除了存储用户信息和密钥之外,建立一张表用于存储和管理近期的动态口令。
假设约定动态口令的有效期为2分钟,每 timeStep=30 秒生成新的动态口令。那么从 t0 开始存入第 1 个动态口令,t0+30s 后存入第 2 个动态口令,t0+60s 后存入第3个动态口令, t0+90s 后存入第4个动态口令,t0+120s 后删除 t0 对应的动态口令,插入第 5 个动态口令,以此类推,数据库中总是存有 1~4 个有效的动态口令。当服务端收到一次动态口令认证请求时,只要传入的口令与数据库中存储的该用户对应的任意一个动态口令相同,那么验证成功,并删除数据库中相应的口令记录(表示已经使用过,宣布失效)。
这样只要用户在提交验证请求前 90秒内获取动态口令那么验证肯定是成功的。如果是提交验证请求前 90~120秒 获取的动态口令,那么可能验证成功,也可能失败。
② 动态口令的另一种使用方式:消息下发
消息下发的形式最常见的就是短信、邮件、IM。
服务端根据用户信息、时间和密钥生成并临时存储动态口令,然后将口令发送给用户,在约定的时间内用户提交收到的动态口令,服务端在比对后判定验证结果。
③ 事件驱动的动态口令生成和传递机制:
如上所述,如果服务器每隔 timeStep 时间就得更新一下数据库里所有用户的动态口令,由于动态口令只有在用户登录认证的时候才会使用,那么势必带来计算浪费。
一个变通的实践方法是:
当服务端收到认证请求时即时生成4个动态口令,这4个口令分别对应当前时间 t0、t0-30s、t0-60s、t0-90s 四个时间点对应的动态口令。
查找验证历史记录表,找出相应用户在时间大于 t0-90s 的范围内的验证成功的记录,在4个动态口令中去除已经验证过的口令。
然后将用户提交的口令与最近的 0~4 个有效口令进行比对,如果无一匹配那么验证失败;如果有一个匹配那么验证成功,并将对应的验证事件(用户,动态口令,验证结果,时间)存入数据库。
考虑认证服务和安全监控的需求,可以将每次认证(不论成功、失败,或用户提交了已经使用过的动态口令)事件都记录下来,用于帮用户分析认证失败的原因,以及监控动态口令可能被劫持盗用等行为。