最近,因为领导的重视,所以我现在负责的项目对平台安全性进行了全面的加强。其中就涉及到了『二次验证』。当时在选型过程中开发的同事提出了两个方案:基于短信验证码技术和基于Google账户两步验证技术。
对于基于短信验证码技术的验证很好理解,但是基于Google账户两步验证技术可是很有说道。
什么是二次验证
我们经常为不同的网站设置相同的登录名和密码,但这样的风险很大。一旦某一个网站的账号密码泄露,就会危及到使用相同的密码的其他网站账户的安全,最近多家有『影响力』的网站都爆出了密码泄露事件,账号的安全问题不得不摆上我们的议程。为了解决密码泄露所造成的这个问题,一些网站在登录的时候会要求用户在输入账户和密码之外,再输入另一个一次性密码。比如银行所有的K宝、电子口令卡,支付宝的短信验证码、网易的将军令等就是这种一次性密码的例子。
Google很早之前就开始推荐用户启用『两步验证』(2-Step Verification)功能来实现用户Google账号的安全。并且提供了除通过短信和电话发送一次性验证码外的另外一种一次性口令验证方式,这就是我们今天要说的Google验证(Google Authenticator)技术。这个技术只需要在手机上安装一个口令生成应用程序,就可以生成一个变化着的一次性口令,用于账户验证,并且这个应用程序不需要联网即可使用,很有意思。
Google验证的技术基础
Google验证(Google Authenticator)技术基于的是一种名为散列消息认证码(Hash-based message authentication code,简称HMAC)的密码学技术。
HMAC技术
是一种通过特别计算方式之后产生的一小段信息,用来作为身份认证的方式。
这是Wikipedia上关于SHA-1 HMAC产生过程的图示。
HMAC技术有这样一个数学公式:
其中:
H为密码散列函数(如MD5或SHA-1)
K*为密钥(secret key)
m是要认证的消息
K'是从原始密钥K导出的另一个秘密密钥(如果K短于散列函数的输入块大小,则向右填充(Padding)零;如果比该块大小更长,则对K进行散列)
|| 代表串接
⊕ 代表异或(XOR)
opad 是外部填充(0x5c5c5c…5c5c,一段十六进制常量)
ipad 是内部填充(0x363636…3636,一段十六进制常量)
在具体的使用上,有两个非常重要的协议:HOTP(HMAC-based One-time Password Algorithm,基于HMAC的一次性口令算法)和TOTP(Time-based One-time Password Algorithm,基于时间的一次性口令算法)。这两个协议的不同点就在于其采用的动态因素的不同。
HOTP协议
HOTP又称『事件同步』,其工作原理是:客户端和服务器端事先协商好一个密钥K,用于一次性口令的生成过程,此密钥不被任何第三方所知道。此外,客户端和服务器各有一个计数器C,并且事先将计数值同步。进行验证时,客户端对口令和计数器的组合(K,C)使用HMAC算法计算一次性口令,公式如下:
HOTP(K,C) = Truncate(HMAC-SHA-1(K,C))
这里采用的是SHA-1 HMAC,当然也可以采用MD5 HMAC等加密算法。HMAC算法得出的值位数比较多,不方便用户输入,因此需要截断(Truncate)成为一组不太长十进制数(例如6位)。计算完成之后客户端计数器C计数值加1。用户将这一组十进制数输入并且提交之后,服务器端同样的计算,并且与用户提交的数值比较,如果相同,则验证通过,服务器端将计数值C增加1。如果不相同,则验证失败。
这里的一个比较有趣的问题是,如果验证失败或者客户端不小心多进行了一次生成口令操作,那么服务器和客户端之间的计数器C将不再同步,因此需要有一个重新同步(Resynchronization)的机制。这里不作具体介绍,详情可以参看RFC 4226。
TOTP协议
既然上边说完了HOTP协议,那么TOTP协议也好理解了。TOTP协议,又称『时间同步』,就是把HOTP中的计数器C用当前时间T来代替,于是就可以得到一个随着时间变化而变化的一次性口令。
Google验证的原理
说了这么多,又是密码学又是什么协议。那Google验证到底用了什么技术来实现的呢?
据说Google验证(Google Authenticator)支持HOTP和TOTP两种协议,所以他可以基于时间和基于计数生成两种口令。不过我们用的最多的还是TOTP协议,毕竟对于目前的环境来说,基于时间要比基于计数器更方便一些。
Google验证的实现原理其实很简单:
- 用户在创建手机端身份验证系统时需要获取由服务器生成并保存到数据库中的共享密钥。获取的方式包括用识别程序扫描给定二维码或者直接手动输入。一般我们采用的是扫描二维码来实现,二维码的内容为:
otpauth://totp/用户名?secret=32位密钥&issuer=密钥生成网站
- 一般在第一次绑定时,服务器端的程序会在手机端获取这个密钥后,要求输入根据TOTP协议生成一个6位数的口令。通过比对这个口令的正确与否来判断服务器端与手机端的时间是否一致。如果一致则可以将账号和该密钥进行绑定。手机端也将32位密钥保存到手机上。
- 用户在需要登录时,客户端会每30s使用这个32位密钥和时间戳生成一个6位数字的一次性口令。用户通过Google Authenticator App来查询一次性口令,并在登录时输入。
- 服务器端根据数据库中保存的32位密钥和时间戳通过TOTP协议生成一个6位口令。我们知道,如果算法相同、密钥相同、变量(即时间戳)相同,那么客户端和服务器端计算出的一次性密钥应该是一样的。服务器验证如果一样,则登录成功。
应用场景
- 管理后台的登录:考虑到平台管理后台的安全性,我们在管理后台登录时利用Google验证技术实现了基于TOTP的『登录二次验证』,确保后台账户的安全。
- 重要数据查询授权:在一些涉及平台数据和资金安全的重要功能上,我们也加入了基于此项技术的二次验证机制。比如客服人员需要查询某一用户的资金情况,这时他需要向上级请求一个一次性查询口令。该口令同样基于TOTP来生成。
- 用户端二次验证:未来我们可能会在用户端的登录或资金操作上同样采取『二次验证』的模式来确保用户的账户和资金安全。目前像OKCoin等网站已经开始采用这样的方法来防范安全隐患了。
关于TOTP的一点补充:
- 由于对6位一次性口令的计算是在手机端和服务器端分别完成,,这就不需要联网即可工作。同时不需要传输口令,防止在传输过程中造成口令泄露。
- 一般情况下,我们的手机会自动获取运营商提供的UTC时间,服务器也会从NTP(网络时间协议)服务器上获取UTC时间。这就保证了手机端和服务器端时间的一致性。
- 之所以选择30s作为生成一次6位口令的时间,是因为时间每时每刻都在变化,如果选择一个变化太快的T(例如从某一时间点开始的秒数),那么用户来不及输入密码。如果选择一个变化太慢的T(例如从某一时间点开始的小时数),那么第三方攻击者就有充足的时间去尝试所有可能的一次性密码(试想6位数字的一次性密码仅仅有10^6种组合),降低了密码的安全性。除此之外,变化太慢的T还会导致另一个问题。如果用户需要在短时间内两次登录账户,由于密码是一次性的不可重用,用户必须等到下一个一次性密码被生成时才能登录,这意味着最多需要等待59分59秒!这显然不可接受。综合以上考虑,Google选择了30秒作为时间片,T的数值为从Unix epoch(1970年1月1日 00:00:00)来经历的30秒的个数。
- 由于网络延时,用户输入延迟等因素,可能当服务器端接收到一次性密码时,T的数值已经改变,这样就会导致服务器计算的一次性密码值与用户输入的不同,验证失败。解决这个问题个一个方法是,服务器计算当前时间片以及前面的n个时间片内的一次性密码值,只要其中有一个与用户输入的密码相同,则验证通过。当然,n不能太大,否则会降低安全性。
以上就是Google两步验证的工作原理,同时也推荐大家在支持此功能的网站上启用『二次验证』,因为这确实是保护账户安全的利器。
参考资料
- TOTP: Time-based One-time Password Algorithm, RFC 6238,https://tools.ietf.org/html/rfc6238?spm=5176.doc28667.2.3.HkdaTr
- HOTP: An HMAC-Based One-Time Password Algorithm, RFC 4226, http://tools.ietf.org/html/rfc4226
- Google Authenticator project, https://github.com/google/google-authenticator
- Google账户两步验证的工作原理https://blog.seetee.me/post/2011/google-two-step-verification/