我們往往會在不同的網站上使用相同的密碼,這樣一旦一個網站賬戶的密碼洩露,就會危及到其他使用相同密碼的賬戶的安全,這也是最近的密碼洩露事件造成如此大影響的原因。為了解決這個問題,一些網站在登入時要求除了輸入賬戶密碼之外,還需要輸入另一個一次性密碼。銀行常用的動态密碼卡就是這種一次性密碼的例子,線上支付網站的一次性短信密碼則是另一種實作。
<a target="_blank"></a>
google的兩步驗證算法源自另一種名為hmac-based one-time password的算法,簡稱hotp。hotp的工作原理如下:
用戶端和伺服器事先協商好一個密鑰k,用于一次性密碼的生成過程,此密鑰不被任何第三方所知道。此外,用戶端和伺服器各有一個計數器c,并且事先将計數值同步。
hotp(k,c) = truncate(hmac-sha-1(k,c))
上面采用了hmac-sha-1,當然也可以使用hmac-md5等。hmac算法得出的值位數比較多,不友善使用者輸入,是以需要截斷(truncate)成為一組不太長十進制數(例如6位)。計算完成之後用戶端計數器c計數值加1。使用者将這一組十進制數輸入并且送出之後,伺服器端同樣的計算,并且與使用者送出的數值比較,如果相同,則驗證通過,伺服器端将計數值c增加1。如果不相同,則驗證失敗。
這裡的一個比較有趣的問題是,如果驗證失敗或者用戶端不小心多進行了一次生成密碼操作,那麼伺服器和用戶端之間的計數器c将不再同步,是以需要有一個重新同步(resynchronization)的機制。這裡不作具體介紹,詳情可以參看rfc 4226。
介紹完了hotp,time-based one-time password(totp)也就容易了解了。totp将hotp中的計數器c用目前時間t來替代,于是就得到了随着時間變化的一次性密碼。非常有趣吧!
雖然原理很簡單,但是用時間來替代計數器會有一些特殊的問題,這些問題也很有意思,我們選取幾個進行一下探讨。
首先,時間t的值怎麼選取?因為時間每時每刻都在變化,如果選擇一個變化太快的t(例如從某一時間點開始的秒數),那麼使用者來不及輸入密碼。如果選擇一個變化太慢的t(例如從某一時間點開始的小時數),那麼第三方攻擊者就有充足的時間去嘗試所有可能的一次性密碼(試想6位數字的一次性密碼僅僅有10^6種組合),降低了密碼的安全性。除此之外,變化太慢的t還會導緻另一個問題。如果使用者需要在短時間内兩次登入賬戶,由于密碼是一次性的不可重用,使用者必須等到下一個一次性密碼被生成時才能登入,這意味着最多需要等待59分59秒!這顯然不可接受。綜合以上考慮,google選擇了30秒作為時間片,t的數值為從unix epoch(1970年1月1日 00:00:00)來經曆的30秒的個數。
第二個問題是,由于網絡延時,使用者輸入延遲等因素,可能當伺服器端接收到一次性密碼時,t的數值已經改變,這樣就會導緻伺服器計算的一次性密碼值與使用者輸入的不同,驗證失敗。解決這個問題個一個方法是,伺服器計算目前時間片以及前面的n個時間片内的一次性密碼值,隻要其中有一個與使用者輸入的密碼相同,則驗證通過。當然,n不能太大,否則會降低安全性。
事實上,這個方法還有一個另外的功能。我們知道如果用戶端和伺服器的時鐘有偏差,會造成與上面類似的問題,也就是用戶端生成的密碼和服務端生成的密碼不一緻。但是,如果伺服器通過計算前n個時間片的密碼并且成功驗證之後,伺服器就知道了用戶端的時鐘偏差。是以,下一次驗證時,伺服器就可以直接将偏差考慮在内進行計算,而不需要進行n次計算。
以上就是google兩步驗證的工作原理,推薦大家使用,這确實是保護帳戶安全的利器。
totp: time-based one-time password algorithm, rfc draft, http://tools.ietf.org/id/draft-mraihi-totp-timebased-06.html
hotp: an hmac-based one-time password algorithm, rfc 4226, http://tools.ietf.org/html/rfc4226
實作google authenticator功能需要伺服器端和用戶端的支援。伺服器端負責密鑰的生成、驗證一次性密碼是否正确。用戶端記錄密鑰後生成一次性密碼。
目前用戶端有:
1.伺服器随機生成一個類似于『dpi45hkisexu6hg7』的密鑰,并且把這個密鑰儲存在資料庫中。
2.在頁面上顯示一個二維碼,内容是一個uri位址(otpauth://totp/賬号?secret=密鑰),如『otpauth://totp/[email protected]?secret=dpi45hcebcjk6hg7』,下圖:

3.用戶端掃描二維碼,把密鑰『dpi45hkisexu6hg7』儲存在用戶端。
1.用戶端每30秒使用密鑰『dpi45hkisexu6hg7』和時間戳通過一種『算法』生成一個6位數字的一次性密碼,如『684060』。如下圖android版界面:
2.使用者登陸時輸入一次性密碼『684060』。
3.伺服器端使用儲存在資料庫中的密鑰『dpi45hkisexu6hg7』和時間戳通過同一種『算法』生成一個6位數字的一次性密碼。大家都懂控制變量法,如果算法相同、密鑰相同,又是同一個時間(時間戳相同),那麼用戶端和伺服器計算出的一次性密碼是一樣的。伺服器驗證時如果一樣,就登入成功了。
2.是以,你在自己的項目可以輕松加入對google authenticator的支援,在一個用戶端上顯示多個賬戶的效果可以看上面android版界面的截圖。目前dropbox、lastpass、wordpress,甚至vps等第三方應用都支援google authenticator登陸,請自行搜尋。
3.現實生活中,網銀、網絡遊戲的實體動态密碼牌其實原理也差不多,大家可以自行腦補下,謝謝。
原文釋出時間為:2014-03-06
本文來自雲栖社群合作夥伴“linux中國”