1. 程式人生 > 實用技巧 >認證系統幾種方式

認證系統幾種方式

認證系統幾種方式

https://zhuanlan.zhihu.com/p/50763432

認證(authentication)和授權(authorization)是經常一起出現的詞彙,它倆用途截然不同,認證解決的是如何確定使用者的身份,授權是在認證之後,知道使用者身份的前提下,決定一個使用者擁有什麼許可權。

認證系統包含兩個方面:密碼儲存方案和認證協議,密碼如何儲存會限制可供選擇的認證協議。

1 密碼儲存方案

很多人都傾向於使用相同或者類似的密碼登入不同服務,因此密碼儲存方案的核心需求是原始明文密碼不能被容易的恢復出來,以免殃及其它服務。可惜實際上不同服務由不同方控制,同一密碼用不同儲存方案儲存,安全性取決於最弱的那個。好的服務採用 BCrypt,架不住糟糕的服務用明文儲存同一個密碼,所以還是需要使用者做到一站一密,或者服務自己做多因子認證,至於採用安全性好的密碼儲存方案,這是對一個認證系統的基本要求。

實際工程實現中,密碼如何儲存經歷很多變遷,可惜的是業界並不總是能吸取教訓的,2002 年釋出的 Mac OS X 10.2 使用的密碼加密演算法跟 1979 年 Unix第七版的演算法是一樣的,而且沒有采用類似 /etc/shadow 的機制,這個機制是 UNIX 在 1987 年引入的,到了 Mac OS X 10.3 的時候,Apple 更改了演算法,引入了 shadow 機制,但卻沒有使用 salt。大公司尚且如此馬虎,何況網際網路界多如牛毛的中小公司了,時至今日,肯定有一小撮公司使用明文儲存密碼,一大撮公司用通用摘要演算法或者不用 salt,肯定有好大一批公司把使用者詳細資訊跟密碼儲存在一起,web server 執行的賬戶 www 或者 nobody能讀取所有密碼。

理想的做法是把使用者資訊(使用者名稱到手機號、郵箱、性別等的對映)和認證資訊(使用者名稱到密碼的對映) 分開存放到不同系統裡,尤其是跟業務系統分開,因為業務系統涉及到繁雜的業務邏輯,出問題的概率最大。

Salt 的作用是避免詞典攻擊,所謂詞典就是預先算好的摘要值到密碼的對映表,大家的密碼一般比較短,所以這個表文件可以不大但覆蓋巨量密碼,有了 salt 後,這個對映就成了 password + salt -> hash,變相的提升了密碼長度,只要不同認證系統的 salt 不一樣,就沒法查表反推原始密碼了。看起來有了 salt 後就相當安全了,但其實沒有解決暴力破解問題,如果摘要演算法是MD5並且密碼長度只有六個字元時,暴力破解一個密碼只需要一分鐘,

,這就是為什麼會有人設計專門針對密碼的雜湊演算法。

1.1 明文

儲存明文密碼對認證協議的限制是最寬泛的,但此種方案顯然是無法滿足上面說的核心需求。

1.2 摘要值

早期 UNIX 限制密碼長度,採用加密演算法如 DES 對原始密碼加密儲存,現在的認證系統都使用摘要演算法,對密碼以及一個隨機串(稱為 salt)應用一個摘要演算法,比如 MD5, SHA1, BCrypt,然後儲存摘要值以及salt。這種方式是最廣泛採用的,但在選擇摘要演算法時要避免通用摘要演算法,因為他們的設計考慮了要快速運算,不利於提高暴力破解的難度,應該選用特別針對密碼設計的摘要演算法,這類演算法被稱為key derivation function(KDF),其原理都是通過可配置的迭代次數調節計算量,比通用摘要演算法慢幾個數量級。

更寬泛點說,依靠生物資訊識別使用者,也可以認為是把生物資訊做了個摘要儲存起來,比如指紋,提取指紋特徵很容易,但依據這些特徵反推指紋出來是很難的,而暴力破解就更難了,單次驗證的時間已經在秒甚至分鐘級別,只能以偷摸或者暴力的手段直接獲取“生物體”本身或其克隆了,這在電影裡很常見:-D

下面的三種 KDF,理論上安全強度 SCrypt > BCrypt > PBKDF2,推薦使用 BCrypt。

1.2.1 PBKDF2

Password-Based Key Derivation Function 2, 由 RSA 實驗室提出。這個演算法各個主流語言都有實現(),應用也非常廣泛,比如 WPA/WPA2, WinZip, LastPass, Mac OS X, iOS, Android, Django, Zend, GRUB 2。

PBKDF2 的缺陷是容易被特製的晶片破解,比如使用 ASIC 或者 GPU,所需的電路和 RAM 都很小。

1.2.2 BCrypt

BCrypt 基於 Blowfish 加密演算法設計,Blowfish 是密碼學界泰山北斗級人物 Bruce Schneier 的大作。跟所有的 KDF 一樣,Bcrypt 也是通過調整迭代次數降低運算速度。OpenBSD 率先採用 BCrypt 儲存 /etc/shadow 裡的密碼, PHP 5.5 的 password_hash 函式已經把 BCrypt 作為預設演算法,網際網路界各路專家推薦,各家公司採用,儼然已經成為密碼摘要儲存的標準方案。

BCrypt 被特製晶片破解的難度稍微大點,所需的電路和 RAM 比 PBKDF2 要多些,但依然是固定的。

1.2.3 SCrypt

SCrypt 特意增大了運算所需記憶體,因此提高了特製晶片破解的成本,此演算法被萊特幣(Litecoin) 所使用,但似乎在網際網路界並不廣泛,可能是大家剛轉向 BCrypt,沒工夫搭理尚顯年青(2012 年被提出)的SCrypt,或者是採用 SCrypt的價效比對網際網路企業不合算。

1.3 一次一密演算法的引數

在 OTP(One-time password) 演算法中,需要在服務端和客戶端儲存一些引數或者狀態,隨後認證過程中雙方才能使用同樣的祕鑰函式計算出當前祕鑰。

1.4 客戶端密碼儲存

頻繁輸入密碼會讓使用者很惱火,有兩種辦法解決:在客戶端儲存一個 cookie,標記其登入過,並設定過期時間;讓客戶端儲存密碼並自動輸入。Web 瀏覽器是典型的儲存密碼並自動輸入的例子,可惜的是伊預設不強制要求設定 master password 以保護那些儲存的密碼,在設定對話方塊裡就能看到明文密碼。。。。

多說兩句 cookie。現如今絕大多數網站在用 https 登入後會傳給瀏覽器一個 cookie,記錄其 session id 什麼的,然後轉用 http 協議。這是個非常滑稽的事情,知道 Firesheep() 的教訓的人依然非常少,很少人意識到這是個巨大的安全漏洞。在有線區域網內,處於同一個 VLAN 的使用者是可以抓到這個 VLAN 裡的所有資料包的。在採用 WEP 認證的無線網裡所有人共用一個祕鑰加密網路流量,每個登入成功的人可以破解所有網路流量。在 WPA/WPA2 認證的網路裡,雖然每個使用者的會話祕鑰各不相同,但在 WPA-PSK/WPA2-PSK 模式裡攻擊者可以比較容易的通過 reauth 攻擊得到別人的會話祕鑰,被攻擊者一般毫不知情(掉線了會自動重連),只有 WPA-Enterprise/WPA2-Enterprise 模式(在無線路由管理介面上一般標記為 WPA/WPA2)沒有這個問題,但在家庭網路、咖啡館、麥當勞這些地方全都用的 PSK 模式,只要一臺機器搞鬼或者中木馬,整個網路都不安全。一旦竊取到 cookie,那就完全可以扮作被害人操作網站,由於這種攻擊是在同一個區域網內,公網 IP 是一樣的,服務端也很難區分出來。竊聽流量獲取 cookie 還是一種被動攻擊,如果主動攻擊 http 流量,插入或者替換 JS 指令碼,被攻擊者上當了都難以發覺。總之,在用 WEP、WPA-PSK/WPA2-PSK 認證的地方使用重要服務時,最好保持 https 或者使用 VPN。Firefox 有兩個外掛可以強制對某些域名使用 https:

可惜,全面預設 https 的風氣還沒興盛起來,有的大網站居然都沒處理好https,比如訪問就會發現伊們的 X509 證書是簽發給 s.tbcdn.cn的,Web 瀏覽器發現域名不匹配就會警告,可憐這還是從 VeriSign 購買的證書,你們沒錢買足夠的證書就從申請個免費的嘛。。。。

BTW,看 Firesheep 介紹時順帶了解了下 WPA 的安全性,居然發現 WPS 有重大漏洞,無語了:各位看客趕緊檢查自己的無線路由。。。。

也有專門的軟體儲存密碼,有些還能自動輸入密碼到其它軟體。LastPass() 應該是最有名的了,但把密碼放在它的伺服器上總是有點不大放心,伊也曾爆過安全醜聞。類似功能的開源替代品很多,密碼被加密後放在本地,雖然遷移不方便,但終歸是蛋捏自己手裡:

  • KeePass:http://keepass.info,1.x 版只能用於 Windows 系統, 2.x 版本使用 Mono 編寫,支援 Windows/Linux/MacOS X,手機上也有它的各種移植、相容版本:
  • KeeFox:,Firefox擴充套件,連線 Firefox 和 KeePass
  • KeePassX:, KeePass 1.x 的 Linux 移植版,使用 Qt 庫編寫,支援 Windows/Linux/MacOS X
  • KWallet:, KDE 桌面環境的密碼管理器
  • Seahorse:, Gnome 桌面環境的密碼管理器
  • Mac OS X keychain: Mac OS X 自帶的密碼管理器
  • VIM:VIM 編輯器的 -x 選項可以用來建立加密檔案,一旦加密後下次 VIM 開啟時會自動識別出來是加密的檔案並詢問密碼。使用這個特性時記得設定 encryptmethod 選項為 blowfish。我一直用這個,查詢、編輯都很順手,VIM 做文字操作就是快捷!

2 認證協議

使用者註冊時選擇密碼並被服務端儲存,隨後使用者訪問這個服務時就要經過認證協議。密碼如何儲存是服務內部的問題,除非服務被攻破,儲存的密碼洩露,否則還是不大容易捅簍子的,而認證協議解決的是在網路兩頭雙方的資訊交換,這就太容易出問題了,所以出現繁多的認證協議也就不足為怪。

2.1 雙方認證和三方認證

從認證涉及的各方來看,認證協議分為雙方認證協議,客戶端和服務端,這是最常見的,還有三方認證協議,除了客戶端和服務端還引入了一個雙方都信任的第三方,比如 Kerberos, CAS(Central Authentication Service), OpenID。下面只看雙方認證協議,這也是三方認證的基礎。

2.2 單因子認證和多因子認證

雙方認證按照認證時提供的憑證個數分為單因子認證(SFA, single-factor authentication)和多因子認證(MFA, multi-factor authentication),而多因子認證裡以雙因子認證最為常見。

所謂多因子,指認證時需要提供如下憑證:

  1. knowledge factor (something only the user knows),比如密碼,某個祕密問題的答案,某個特定模式比如Android上鎖屏解鎖時的特定滑動序列。
  2. possession factor (something only the user has),比如銀行卡, RSA SecurID, 各種銀行提供的電子令牌,YubiKey,以及人手一部的手機,各種認證系統往手機發送認證碼就是假定了持有手機的人是目標使用者。
  3. inherence factor (something only the user is),比如指紋,視網膜,語音。

也有人提第四個因子,somebody you know。

單因子認證一般只需要提供第一個因子(也有隻需要第二個因子的,譬如通過目測護照識別身份),雙因子一般是提供第一個和第二個因子。多因子認證並不是絕對安全,只是增加被破解的難度。注意有一些認證系統看起來像是雙因子認證,其實是單因子認證,比如登入時除了密碼,還要回答一個祕密問題,或者可以通過緊急郵箱找回密碼,這種做法通過讓使用者提供多個knowledge factors 提高破解難度。

在網上登入絕大部分情況下都是單因子認證,一個密碼了事。使用網上銀行或者 ATM 機是典型的雙因子認證,除了密碼之外還要提供電子令牌上的數字或者銀行卡,雙因子認證方案裡幾乎不可能被完全不相關的黑客拿到兩個因子。

雙因子認證很容易在實現時引入漏洞:丟失密碼後使用手機發簡訊即可重置密碼(要求額外提供身份證號是不保險的,身份證號並不是保密資訊),或者手機上的應用長期快取第一個因子,只需要提供第二個因子。在安全和方便之間總是難以皆大歡喜。

2.3 互相認證 (mutual authentication)

關於認證還有另外一個問題,大家都明白客戶端需要向服務端證明自己身份,很多人可能忽視了服務端向客戶端證明自己身份的必要性,這點一旦點出,大家自然明白,好比影視裡倆同志接頭要互報口令。這也是為什麼稍微正經的認證系統登入時需要用 SSL/TLS 的原因之一(另一個重要原因是加密通訊避免使用者密碼在傳輸時被竊聽),利用 SSL/TLS 客戶端檢查服務端的 X509 證書。

2.4 協議

具體的認證協議五花八門,可以一刀切分為兩類:需要向對方傳送密碼的,不管是固定密碼還是一次一密;不需要向對方傳送密碼的。

2.4.1 需要向對方傳送密碼的認證協議

這種協議都需要 SSL/TLS 之類的協議護駕,否則毫無安全可言。

2.4.1.1 HTTP Basic, SMTP PLAIN, SMTP LOGIN

把使用者名稱、明文密碼或者BASE64編碼的密碼傳送給服務端。這個是最容易實現的,在 HTML 登入表單裡也很好做,服務端可以拿密碼跟明文密碼比對(不推薦),或者拿提供的密碼與 salt 做摘要再跟期望的摘要值比較。

2.4.2 不需要向對方傳送密碼的認證協議

2.4.2.1 CRAM-MD5

Challenge Response Authentication Mechansim, 所謂的 challenge 就是服務端發給客戶端一個隨機字串,客戶端需要用密碼或者密碼的摘要值對其進行 HMAC-MD5 運算,然後把結果傳送給服務端,服務端對隨機串做相同運算並比對結果。

此協議只是客戶端向服務端認證,沒有服務端向客戶端認證,因此一般需要 SSL/TLS 護駕,讓客戶端驗證服務端證書。具體實現時那個 challenge 往往有比較固定的模式,沒有 SSL/TLS 通道加密的話,通訊資料包被竊聽後易受詞典攻擊。

在服務端,密碼要麼是存為明文,要麼是存為 MD5 摘要值或者中間運算結果,一是容易被暴力破解,二是儲存的值在 CRAM-MD5 認證協議裡跟密碼等價,所以拿到這個摘要值其實就是獲得了此使用者的許可權。

2.4.2.2 DIGEST-MD5

相比 CRAM-MD5,在認證過程中允許客戶端提供一個隨機串新增在伺服器給定的隨機串上,因此避免了惡意的服務端做選擇明文攻擊(CRAM-MD5 中對選定明文,客戶端返回的摘要值是確定的,因此可以被詞典攻擊)。

DIGEST-MD5 支援互相認證,但協議本身選項比較多,容易實現不當,互操作性比較差。

雖然攻擊難度比 CRAM-MD5 大,但一般也需要用 SSL/TLS 保護通道以免竊聽。

跟 CRAM-MD5 一樣,密碼是 MD5 摘要,而且在認證協議裡等價於密碼,因此在服務端儲存的密碼是相當不安全的。

2.4.2.3 SCRAM (Salted Challenge Response Authentication Mechanism)

SCRAM 是一族演算法,最常見的是 SCRAM-SHA1,設計用來替換 DIGEST-MD5。 SCRAM 比 DIGEST-MD5 更安全也更容易實現,XMPP 把 SCRAM 列為必須支援的認證協議。雖然 SCRAM 規定 SHA1 為必須支援的摘要演算法,但 SCRAM 並不限制摘要演算法,可以使用 IANA 規定的任何演算法:

SCRAM 認證是互相認證。儲存在服務端的 StoredKey 是 PBKDF2 摘要演算法的結果的再次摘要,在 SCRAM 中不是 password 的等價物,即使洩露也不能偽裝使用者,但儲存在服務端的 ServerKey 一旦洩露,攻擊者可以偽裝服務端。

SCRAM 需要搭配 channel binding 以避免中間人攻擊,可以用 SSHv2 和 TLS。所謂通道繫結就是應用層的認證協議利用傳輸層的加密協議,確認在應用層認證的雙方確實是互相通訊的雙方,避免中間人攻擊,注意這裡的通道繫結是需要兩層協議的具體實現互相支援的,比如上層協議要獲取 SSHv2 的 session ID 或者 TLS 裡的握手報文內容(tls-unique binding)、X509 證書 (tls-server-end-point binding) 參與認證過程,舉例來說,在 TLS 上做通道繫結的 SCRAM-SHA-1 增強版叫 SCRAM-SHA-1-PLUS, 其實現需要 OpenSSL 或者 GnuTLS 庫提供獲取握手報文內容、X509 證書的 API。

2.4.2.4 SRP (Secure Remote Password protocol)

與 Kerberos 和 SSL X509 不同,SRP 並不依賴第三方的受信祕鑰服務或者證書分發機構,SRP 使用共享密碼做互相認證。SRP 有大量優良特性:

  • 容許弱密碼,攻擊者必需跟服務端或者使用者端互動才能暴力破解
  • 服務端儲存的 salted password 不是密碼的等價物,而是類似於公鑰;
  • 認證過程不需要傳輸層加密
  • 認證過程可以生成一個會話祕鑰

參考:

OpenSSL >= 1.0.1 以及 Apache 2.5 mod_ssl, mod_gnutls 支援 TLS-SRP:

但是很不幸 Redhat 為了避免可能的專利糾紛刪除了 Fedora、RHEL 中 openssl 軟體包裡的 srp 程式碼:

2.4.2.5 AKA

Authentication and Key Agreement,用於 3G 網路中,提供互相認證以及加密通道。

2.4.2.6 EAP

Extensible Authentication Protocol,EAP 是一個認證框架,常用於無線網以及點對點網路中。具體的認證方法稱為 EAP method,目前定義了大約四十種。

EAP-TLS: 使用 client & server X509 certificates互相認證,並用TLS加密通道

EAP-POTP: 使用 OTP token 做雙因子認證

EAP-PSK: 使用 pre-shared key 做互相認證,認證成功後通道被加密

EAP-PWD: 從一系列共享密碼中挑選一個做認證,被 Android 4.0, FreeRADIUS, Radiator 支援

EAP-IKEv2

EAP-FAST

EAP-AKA

PEAP: 為 EAP 提供加密保護

2.4.2.7 RADIUS

基於 UDP 協議。在使用 WPA-Enterprise/WPA2-Enterprise 無線網認證方式的地方就需要 RADIUS 服務。

2.4.2.8 TACACS+

Cisco 開發,基於 TCP 協議,提供 authentication/authorization/accounting.

2.4.2.9 Diameter

代替 RADIUS,提供 authentication, authoriazation, accounting。

2.4.3 雙因子認證

以前雙因子認證還是個高階貨,只用在網上銀行以及大型企業 IT 系統的登入系統裡,在 Google 推出雙因子認證後, Internet 巨頭們紛紛支援,加上智慧機普及,雙因子認證算是飛入尋常百姓家了。

上面提到 SRP、SCRAM,看起來是很安全了,但是總架不住客戶端中了木馬導致密碼洩露,或者密碼比較二被人猜出來,或者一個密碼打天下忽然驚聞常去的某網站居然是明文儲存密碼,等等等等,所以牽涉到使用者深度隱私或者錢財的服務必須自覺的支援雙因子認證。

一般雙因子認證使用這兩個因子: knowledge factor,基本都是指密碼了, possession factor,電子令牌上或者手機上的 Google authenticator 應用顯示的認證碼,或者是服務端通過簡訊發到手機上的認證碼,這個認證碼就是個 one-time password,其生成演算法是有業界標準的,並不是個簡單的隨機數。

Wikipedia 上對 OTP 的講解很清楚:

簡單來說,TOTP 就是雙方共享一個種子,用一個函式對這個種子以及時間原點到當前逝去的分鐘數或者 30s 數目求值,由於種子和時間雙方都一致,所以自然解決了認證碼的過期問題,以及跟使用者對應的問題。這個演算法需要客戶端和服務端的時間偏移不能太大,TOTP 的 RFC 提到如何容忍稍許的時間不同步,根據使用者的輸入記錄時鐘偏移,比如使用者連續三次輸入上一分鐘的認證碼,那麼服務端就知道使用者的時鐘慢了一分鐘。

HOTP 是雙方定一個種子數字,用同一個摘要函式這個種子求值,對結果再次算摘要值,如此反覆,由於摘要函式的特性,很難從下一個值推算出上一個值,所以把這些值倒過來就是一個密碼錶了,每次用下一個密碼。

TOTP 比 HOTP 用的更廣泛,因為 HOTP 每次使用時都有一個當前狀態需要記錄,使用上不大方便,而 TOTP 只需要種子數字以及時間同步,另一個 HOTP 的問題是一旦使用者不小心洩露了後面的密碼,那麼這個密碼之前的密碼都洩露了,通過對後面的密碼做摘要即可得到前面的密碼。

明白原理後就很容易理解 OTP 是怎麼用的了:

  • 起始種子的生成
    • 對硬體形式的電子令牌,生產時會設定好種子到硬體裡並備案,管理員購買後會把種子數字輸入到 OTP server 裡。
    • 對手機上的 OTP 軟體,服務端可以發簡訊、語音到使用者,或者網站上生成種子數字的 QR code,使用者拿手機掃描出來,然後這個種子被輸入到 OTP 軟體裡;
    • 服務端也可以不把種子發給使用者,只是讓使用者註冊手機號,每次使用者要登入時,使用者點選網頁上的獲取驗證碼按鈕,服務端就會把當前的認證碼通過簡訊傳送到使用者的手機上。這種方式安全點,不用擔心使用者方洩漏了種子數字;
    • 在生成種子的時候,服務端還可以生成一系列的 backup codes,這些數字等價於認證碼,用完一個即作廢一個。backup codes 是需要使用者儲存好的,比如打印出來,backup codes 的目的是使用者在丟失手機後可以用 backup code 登入。

  • 認證碼的使用:電子令牌或者手機上的 OTP 軟體會顯示當前的認證碼,使用者在登入服務時需要輸入使用者名稱、密碼、認證碼。注意得到認證碼的過程是本地算出來的,不需要聯絡伺服器。

Google 的認證系統還有個高階功能,可以為一個賬戶生成多個副密碼,這些密碼不需要雙因子認證,這個功能是為了給第三方不支援雙因子認證的應用訪問 Google 服務。

雙因子認證提高了認證系統的安全性,但並不意味著認證過程絕對安全,一旦使用者密碼和某次認證碼洩露(比如通過鍵盤鉤子記錄按鍵,然後切斷使用者和服務端連線),攻擊者可以立馬登入然後修改密碼重新繫結手機重新生成種子,當然這麼搞會被使用者發覺。也可以隱蔽點,如果攻擊者和使用者在同一個內網裡,攻擊者和使用者先後登入,並且Web瀏覽器指紋一樣,服務端是沒法區分的。

Google authenticator 官方自稱 twp-step authentication 而非 two-factor authentication,因為有人詬病它的安全性。傳統意義上的 possession factor 是很難複製的,要麼擁有要麼沒有,比如 RSA SecurID 就是抗篡改的(tamper-resistant ),而 Google authenticator 可以同時在多個裝置上執行,只要把種子數字從手機裡複製出來,這破壞了“something only the user has”的要求。但總之這種 soft token 還是聊勝於無,窮人的福利。

2.4.4 CAPTCHA

CAPTCHA 是 Completely Automated Public Turing test to tell Computers and Humans Apart 的縮寫。為了避免指令碼自動註冊、登入,在註冊或者登入表單裡新增一個輸入框以及小圖片,圖片上顯示一些扭曲的文字,需要使用者肉眼識別出來並填入那個輸入框裡。有的 CAPTCHA 也提供語音輸出。

原理很簡單,實際應用中也很常見,但做好並不容易,需要挖空心思讓機器圖形識別困難,但對人肉識別又比較容易,看起來很凌亂的圖片,未必難於被機器識別。

順帶八卦一下,某些網站的 CAPTCHA 做成了一個廣告圖片,要求輸入廣告裡某個字眼,做法相當高明,使用者不得不看廣告。

3 無責任推薦

依優先順序順序,排在前面的優先順序高。

3.1 Kerberos/SPNEGO, OpenID

Intranet 使用 Kerberos 和 SPNEGO 做 single sign-on,這個選擇已然定論,支援這些協議的作業系統和應用軟體都非常廣泛。

Internet 使用 OpenID,不用自己操心認證的事情了。但用 OpenID 也是有些煩人的因素了,自己網站的使用者登入狀況被第三方知道多少是有點讓人不爽的事情,蛋捏別人手裡。對認證的安全把握也完全沒有,我就從來不敢在手機上的非 Google 應用裡輸入 GMail 賬號資訊,鬼知道密碼是送給李逵還是李鬼了,介面上也沒有什麼 OpenID seal 可供識別,在瀏覽器上還稍微放心點,畢竟瀏覽器是個相對安全的中立方(排除個別別有用心的瀏覽器)。

估計大夥也是這麼想的,所以雖然 OpenID 想法很好,但大家都把它當做錦上添花的特性,不會作為主要的認證方式。

3.2 TLS X509 server certificate + SRP or TLS-SRP

SRP 用於認證本身並不需要 SSL/TLS 保護,但實際應用中需要登入往往意味著需要加密隨後的通訊,TLS-SRP 是 TLS 協議對 SRP 的直接支援。在 TLS 協議中涉及到四類密碼學演算法:

  • 認證:互相識別對方身份,可以用 X509 證書(涉及 RSA or DSA), PSK(pre-shared key), SRP,其中 SRP 可以搭配 RSA、DSA 混用增強安全性;
  • 祕鑰交換:在不可信通道上交換一個共享的對稱加密祕鑰,用於隨後加密連線,可以用 RSA, SRP, DH, ECDH。
  • 加密演算法:加密連線,可以用 3DES, AES 等;
  • 摘要演算法:在認證和祕鑰交換過程會頻繁用到摘要演算法,比如 MD5, SHA1;

可以看出 SSL/TLS 協議並不一定需要 x509 證書,可惜所有 Web 瀏覽器只支援 X509 證書方式的認證,並且對客戶端的 x509 證書認證操作比較麻煩,需要使用者自己在瀏覽器設定裡匯入客戶端自己的證書,所以為了應付 Web 瀏覽器,還是需要結合 TLS X509 server cert 做服務端認證然後加密連線,然後再用 HTML 表單以及 JavaScript 做 SRP 認證(這一步不依賴加密連線),如果是本地應用,可以直接上 TLS-SRP。

TLS-SRP 要求 OpenSSL >= 1.0.1 或者 GnuTLS。 OpenSSL 1.0.1 在 2012 年 3 月 14 日釋出。

SRP 在服務端儲存的是 verifier,而非 password 或者 password 的等價物, verifier 類似公鑰認證裡的公鑰,所以洩露了也太大問題。

3.3 TLS + SCRAM

使用帶有 TLS channel binding 的 SCRAM-SHA-1-PLUS。

3.4 TLS X509 server certificate + PLAIN

實現簡單,理解容易,業界最廣泛使用的方案。用 x509 證書驗證服務端,然後在加密連線上傳輸密碼或者其摘要值給服務端以驗證客戶端。密碼儲存使用 BCrypt。

使用 TLS 的注意事項:

  • 使用 OpenSSL >= 1.0.0 的 ECDHE 特性獲得 perfect forward secrecy 並且保持高效能:
  • 對 native client 使用 TLS >= 1.1 :
  • 對瀏覽器使用 SSL >= 3.0 (包含了 TLS 1.0,雖然有問題但是老版瀏覽器不支援 TLS >= 1.1)。
  • 禁用 TLS compression 和 HTTP compression:
  • TLS 祕鑰交換演算法選擇優先次序:ECDH > DH > RSA(不支援 perfect forward secrecy) > ECDSA(不是所有客戶端都支援)。
  • 讓 server 端決定 cipher 優先順序順序(Apache: SSLHonorCipherOrder On, Apache TrafficServer: proxy.config.ssl.server.honor_cipher_order 1),而非預設的讓 client 決定。
  • 禁止 TLS client 發起的 renegotiation,對於長連線,server 應在一小時之內發起 renegotiation,短連線應禁止 renegotiation。
  • Abbreviated handshake (session ID or session ticket extension)有效時間不超過兩小時。
  • 優先使用 AES 加密演算法,以利用 AESNI 硬體加速。

如果服務端是一個叢集,那麼 TLS session ID 需要搭配 memcached 做共享的 session cache,而 session ticket extension 需要叢集所有機器使用同樣的ticket key。

這個方案有兩個問題,TLS 證書驗證由於使用者可能盲目信任未知證書而導致中間人攻擊;有可能失誤會導致密碼被明文傳輸,譬如客戶端邏輯出錯沒有啟用 TLS 連線,譬如登入頁面沒配置成 https only。

4 實現參考

認證協議是個理解起來傷腦筋,要想實現無誤也很費神的事情,有人就構建了許多框架或者 API 來容納各種認證協議:GSSAPI(Generic Security Services Application Programming Interface),SASL(Simple Authentication and Security Layer), SSPI(Security Support Provider Interface),其中應用最廣的當屬 SASL,眾多網路協議以及 Linux 下無數應用都支援 SASL,不過最遺憾的是 HTTP 協議以及眾多 web 瀏覽器不支援它。

SASL 主流實現有四個:

這些 SASL 實現可以從檔案、OpenLDAP、關係資料庫讀取密碼資訊並進行驗證,也能更改密碼,列舉使用者名稱,在實現認證系統時最好基於某個 SASL 實現。

4.1 TLS X509 server certificate + SRP or TLS-SRP

單純的 SRP 實現在 wikipedia 頁面上列了很多,目前只有 Cyrus-SASL 支援 SRP。 TLS-SRP 需要 OpenSSL >= 1.0.1 或者 GnuTLS 支援。注意 Fedora、RHEL 官方的 openssl、gnutls 軟體包剔除了 SRP 相關程式碼以避免潛在的專利糾紛。

目前沒有 Web 瀏覽器直接支援 SRP,需要先用 TLS x509 server cert 建立 https 連線,然後用 HTML 表單以及 JavaScript 做 SRP 認證。雖然這個 x509 server cert 不用於認證,但還是需要正規 CA 簽發的證書,以免中間人攻擊替換掉 HTML 表單以及 JavaScript 從而導致明文密碼洩露。

非 Web 瀏覽器場合,可以直接上 TLS-SRP,不需要 X509 證書。

下面是分別用 OpenSSL 和 GnuTLS 演示 TLS-SRP。

4.1.1 OpenSSL

$ touch srpvfile.txt
$ openssl srp -srpvfile srpvfile.txt -userinfo "my test user" -add testuser
$ openssl s_server -nocert -cipher SRP -srpvfile srpvfile.txt -accept 4430
$ openssl s_client -srpuser testuser -cipher SRP -connect localhost:4430

4.1.2 GnuTLS

$ srptool --create-conf srppasswd.conf
$ srptool --passwd-conf srppasswd.conf --passwd srppasswd.txt -u testuser
$ gnutls-serv -p 4430 --http --srppasswdconf srppasswd.conf --srppasswd srppasswd.txt --priority NORMAL:-KX-ALL:+SRP:+SRP-DSS:+SRP-RSA
$ gnutls-cli -p 4430 localhost --srpusername testuser --srppasswd 123456 --priority NORMAL:-KX-ALL:+SRP:+SRP-DSS:+SRP-RSA
$ curl --tlsuser testuser --tlspassword 123456 -k https://localhost:4430/

4.2 TLS + SCRAM

SCRAM 的支援程度比 SRP 好點,Dovecot SASL 和 Cyrus SASL 支援 SCRAM-SHA-1,GNU SASL 支援 SCRAM-SHA-1 和 SCRAM-SHA-1-PLUS。

跟 TLS-SRP 一樣,沒有 Web 瀏覽器直接支援 SCRAM-SHA-1-PLUS, 在 Web 瀏覽器上也需要 TLS x509 server cert 先建立 https 連線,然後用 HTML 表單以及 JavaScript 做 SCRAM-SHA-1 認證。同樣,也需要正規 CA 簽發的證書以避免 HTML 和 JS 被中間人替換掉。

非 Web 瀏覽器場合,可以不需要 X509 證書,在 TLS 上啟用 aNULL cipher() 加 SCRAM-SHA-1-PLUS(tls-unique channel binding)做認證。

4.3 TLS X509 server certificate + PLAIN

需要用正規 CA 簽名的 X509 證書,因為這個證書用來驗證服務端身份。

各種 SASL 實現都支援 PLAIN 機制,其實自己實現也非常簡單了,唯一要注意的是最好把認證服務跟業務邏輯所在服務分開,避免業務邏輯所在服務出簍子被人爬下整個密碼庫。

認證系統應該用 https 保護,並設定 HSTS 頭部:

4.4 OTP

OTP 的原理並不複雜,自己實現一個也不難,下面是許多現成的實現供參考。

  • OTPW: OPIE和S/KEY的替代品,但並不相容。提供了 PAM module。其原理是生成幾百個隨機數,前頭拼一個 prefix password 然後計算RIPEMD-160 摘要值並按 BASE64 編碼顯示,使用者需要把這個密碼錶打印出來。這個軟體設計思路以及給人的使用體驗都很古樸:-D
  • Google authenticator:,支援 TOTP 和 HOTP,提供了 PAM module,有 Android, iOS, Blackberry 版本的手機應用。Google 後來不提供它新版的原始碼了,所以有人做了兩個 fork:

沒有一個提供 backup code 特性,當然,這個不在 OTP 原理裡頭,只是具體實現時的一個方便使用者的特性。實現時可以參考 Google authenticator 和 oath-toolkit。

使用 oath-toolkit 和 Google authenticator 可以驗證兩者是一致的,Google 返回的 seed 值是 16 個字元的 base32 編碼的字串,實際上 Google authenticator 不要求必需是 16 個字元。

$ oathtool -b --totp 'bkuq 7tya sdbu jlda'  # 字串的空格被忽略,大小寫無關
200157
$ oathtool -b --totp 'bkuq 7tya sdbu jlda' 200157   # 驗證
0

將那串 base32 編碼字串輸入 Google authenticator 裡,可以驗證它的結果跟 oathtool 生成的認證碼確實是一致的。Google authenticator 可以用於 Google 之外的服務。

5 總結

==========End