圖解HTTP(八)—— 確認使用者身份的認證
一、何為認證
計算機本身無法判斷坐在顯示器前的使用者的身份,為了弄清楚究竟是誰在訪問伺服器,就得讓對方的客戶端自報家門。為確保使用者具有訪問系統的許可權,就需要核對使用者本人才知道的資訊,這就是認證。
1、認證資訊
核對的資訊通常包括以下這些:
① 密碼:只有本人才知道的字串資訊;
② 動態令牌:僅限本人持有的裝置內顯示的一次性密碼;
③ 數字證書:僅限本人(終端)持有的資訊;
④ 生物認證:指紋和虹膜等本人的生理資訊;
⑤ IC卡等:僅限本人持有的資訊。
2、認證方式
HTTP/1.1使用的認證方式如下:
① BASIC認證(基本認證);
② DIGEST認證(摘要認證);
③ SSL客戶端認證;
④ FormBase認證(基於表單認證)。
二、BASIC認證
BASIC認證是從HTTP/1.0開始就定義的認證方式,它是Web伺服器與通訊客戶端之間進行的認證方式。
1、BASIC認證圖解
2、BASIC認證步驟
步驟 1: 當請求的資源需要 BASIC 認證時,伺服器會隨狀態碼 401 Authorization Required,返回帶 WWW-Authenticate 首部欄位的響應。 該欄位內包含認證的方式(BASIC) 及 Request-URI 安全域字串 (realm)。
步驟 2: 接收到狀態碼 401 的客戶端為了通過 BASIC 認證,需要將 使用者 ID 及密碼傳送給伺服器。傳送的字串內容是由使用者 ID 和密碼 構成,兩者中間以冒號(:)連線後,再經過 Base64 編碼處理。 假設使用者 ID 為 guest,密碼是 guest,連線起來就會形成 guest:guest 這 樣的字串。然後經過 Base64 編碼,最後的結果即是 Z3Vlc3Q6Z3Vlc3Q=。把這串字串寫入首部欄位 Authorization 後, 傳送請求。 當用戶代理為瀏覽器時,使用者僅需輸入使用者 ID 和密碼即可,之後, 瀏覽器會自動完成到 Base64 編碼的轉換工作。
步驟 3: 接收到包含首部欄位 Authorization 請求的伺服器,會對認證 資訊的正確性進行驗證。如驗證通過,則返回一條包含 Request-URI 資源的響應。
總結:BASIC 認證雖然採用 Base64 編碼方式,但這不是加密處理。不需要 任何附加資訊即可對其解碼。換言之,由於明文解碼後就是使用者 ID 和密碼,在 HTTP 等非加密通訊的線路上進行 BASIC 認證的過程 中,如果被人竊聽,被盜的可能性極高。 另外,除此之外想再進行一次 BASIC 認證時,一般的瀏覽器卻無法 實現認證登出操作,這也是問題之一。 BASIC 認證使用上不夠便捷靈活,且達不到多數 Web 網站期望的安 全性等級,因此它並不常用。
三、DIGEST認證
為彌補 BASIC 認證存在的弱點,從 HTTP/1.1 起就有了 DIGEST 認 證。 DIGEST 認證同樣使用質詢 / 響應的方式 (challenge/response),但不會像 BASIC 認證那樣直接傳送明文密 碼。 所謂質詢響應方式是指,一開始一方會先發送認證要求給另一方,接 著使用從另一方那接收到的質詢碼計算生成響應碼。最後將響應碼返 回給對方進行認證的方式。
因為傳送給對方的只是響應摘要及由質詢碼產生的計算結果,所以比 起 BASIC 認證,密碼洩露的可能性就降低了。
1、DIGEST認證圖解
2、DIGEST認證步驟
步驟 1: 請求需認證的資源時,伺服器會隨著狀態碼 401 Authorization Required,返 迴帶 WWW-Authenticate 首部欄位的響應。 該欄位內包含質問響應方式認證所需的臨時質詢碼(隨機數, nonce)。首部欄位 WWW-Authenticate 內必須包含 realm 和 nonce 這兩個欄位的 資訊。客戶端就是依靠向伺服器回送這兩個值進行認證的。 nonce 是一種每次隨返回的 401 響應生成的任意隨機字串。該字元 串通常推薦由 Base64 編碼的十六進位制數的組成形式,但實際內容依 賴伺服器的具體實現。
步驟 2: 接收到 401 狀態碼的客戶端,返回的響應中包含 DIGEST 認 證必須的首部欄位 Authorization 資訊。 首部欄位 Authorization 內必須包含 username、realm、nonce、uri 和 response 的欄位資訊。其中,realm 和 nonce 就是之前從伺服器接收到 的響應中的欄位。 username 是 realm 限定範圍內可進行認證的使用者名稱。 uri(digest-uri)即 Request-URI 的值,但考慮到經代理轉發後 Request-URI 的值可能被修改,因此事先會複製一份副本儲存在 uri 內。 response 也可叫做 Request-Digest,存放經過 MD5 運算後的密碼字元 串,形成響應碼。
步驟 3: 接收到包含首部欄位 Authorization 請求的伺服器,會確認認 證資訊的正確性。認證通過後則返回包含 Request-URI 資源的響應。 並且這時會在首部欄位 Authentication-Info 寫入一些認證成功的相關信 息。
總結:DIGEST 認證提供了高於 BASIC 認證的安全等級,但是和 HTTPS 的 客戶端認證相比仍舊很弱。DIGEST 認證提供防止密碼被竊聽的保護 機制,但並不存在防止使用者偽裝的保護機制。 DIGEST 認證和 BASIC 認證一樣,使用上不那麼便捷靈活,且仍達不 到多數 Web 網站對高度安全等級的追求標準。因此它的適用範圍也 有所受限。
四、SSL客戶端認證
從使用使用者 ID 和密碼的認證方式方面來講,只要二者的內容正確, 即可認證是本人的行為。但如果使用者 ID 和密碼被盜,就很有可能被 第三者冒充。利用 SSL客戶端認證則可以避免該情況的發生。 SSL客戶端認證是藉由 HTTPS 的客戶端證書完成認證的方式。憑藉 客戶端證書認證,伺服器可確認訪問是否 來自已登入的客戶端。
1、SSL客戶端認證的步驟
為達到 SSL客戶端認證的目的,需要事先將客戶端證書分發給客戶端,且客戶端必須安裝此證書。
步驟 1: 接收到需要認證資源的請求,伺服器會發送 Certificate Request 報文,要求客戶端提供客戶端證書。
步驟 2: 使用者選擇將傳送的客戶端證書後,客戶端會把客戶端證書信 息以 Client Certificate 報文方式傳送給伺服器。
步驟 3: 伺服器驗證客戶端證書驗證通過後方可領取證書內客戶端的公開金鑰,然後開始 HTTPS加密通訊。
2、SSL客戶端採用雙因素認證
在多數情況下,SSL客戶端認證不會僅依靠證書完成認證,一般會和 基於表單認證組合形成一種雙因素認證(Two-factor authentication)來使用。所謂雙因素認證就是指,認證過程中不僅需 要密碼這一個因素,還需要申請認證者提供其他持有資訊,從而作為 另一個因素,與其組合使用的認證方式。 換言之,第一個認證因素的 SSL客戶端證書用來認證客戶端計算機, 另一個認證因素的密碼則用來確定這是使用者本人的行為。
五、基於表單的認證
基於表單的認證方法並不是在 HTTP 協議中定義的。客戶端會向伺服器上的 Web 應用程式傳送登入資訊(Credential),按登入資訊的驗證結果認證。
1、認證多半為基於表單的認證
由於使用上的便利性及安全性問題,HTTP 協議標準提供的 BASIC 認 證和 DIGEST 認證幾乎不怎麼使用。另外,SSL客戶端認證雖然具有 高度的安全等級,但因為匯入及維持費用等問題,還尚未普及。 比如 SSH 和 FTP 協議,伺服器與客戶端之間的認證是合乎標準規範 的,並且滿足了最基本的功能需求上的安全使用級別,因此這些協議 的認證可以拿來直接使用。但是對於 Web 網站的認證功能,能夠滿 足其安全使用級別的標準規範並不存在,所以只好使用由 Web 應用 程式各自實現基於表單的認證方式。 不具備共同標準規範的表單認證,在每個 Web 網站上都會有各不相 同的實現方式。如果是全面考慮過安全效能而實現的表單認證,那麼 就能夠具備高度的安全等級。但在表單認證的實現中存在問題的 Web 網站也是屢見不鮮。
2、Session管理及Coolie的應用
基於表單認證的標準規範尚未有定論,一般會使用 Cookie 來管理 Session(會話)。 基於表單認證本身是通過伺服器端的 Web 應用,將客戶端傳送過來 的使用者 ID 和密碼與之前登入過的資訊做匹配來進行認證的。 但鑑於 HTTP 是無狀態協議,之前已認證成功的使用者狀態無法通過協 議層面儲存下來。即無法實現狀態管理,因此即使當該使用者下一次繼續訪問,也無法區分他與其他的使用者。於是我們會使用 Cookie 來 管理 Session,以彌補 HTTP 協議中不存在的狀態管理功能。
⑴ 使用Cookie來管理SessionID的步驟
步驟 1: 客戶端把使用者 ID 和密碼等登入資訊放入報文的實體部分, 通常是以 POST 方法把請求傳送給伺服器。而這時,會使用 HTTPS 通訊來進行 HTML表單畫面的顯示和使用者輸入資料的傳送。
步驟 2: 伺服器會發放用以識別使用者的 Session ID。通過驗證從客戶 端傳送過來的登入資訊進行身份認證,然後把使用者的認證狀態與 Session ID 繫結後記錄在伺服器端。 向客戶端返回響應時,會在首部欄位 Set-Cookie 內寫入 Session ID(如 PHPSESSID=028a8c…)。
然而,如果 Session ID 被第三方盜走,對方就可以偽裝成你的身份進 行惡意操作了。因此必須防止 Session ID 被盜,或被猜出。為了做到 這點,Session ID 應使用難以推測的字串,且伺服器端也需要進行 有效期的管理,保證其安全性。 另外,為減輕跨站指令碼攻擊(XSS)造成的損失,建議事先在 Cookie 內加上 httponly 屬性。
步驟 3: 客戶端接收到從伺服器端發來的 Session ID 後,會將其作為 Cookie 儲存在本地。下次向伺服器傳送請求時,瀏覽器會自動傳送 Cookie,所以 Session ID 也隨之傳送到伺服器。伺服器端可通過驗證 接收到的 Session ID 識別使用者和其認證狀態。
⑵ 伺服器安全儲存使用者密碼的一種做法——加鹽
不僅基於表單認證的登入資訊及認證過程都無標準化的方法, 伺服器端應如何儲存使用者提交的密碼等登入資訊等也沒有標準化。 通常,一種安全的儲存方法是,先利用給密碼加鹽(salt)的方式增加額外資訊,再使用雜湊(hash)函式計算出雜湊值後儲存。
① 什麼是加鹽?
可以在密碼中混入一段“隨機”的字串再進行雜湊加密,這個被字串被稱作鹽值。通過加鹽使得同一個密碼每次都被加密為完全不同的字串。為了校驗密碼是否正確,我們需要儲存鹽值。通常和密碼雜湊值一起存放在賬戶資料庫中,或者直接存為雜湊字串的一部分。
鹽值並不需要保密,由於隨機化了雜湊值,查表法、反向查表法和彩虹表都不再有效。攻擊者無法確知鹽值,於是就不能預先計算出一個查詢表或者彩虹表。這樣每個使用者的密碼都混入不同的鹽值後再進行雜湊,因此反向查表法也變得難以實施。
② 正確的生成鹽值
鹽值應該使用基於加密的偽隨機數生成器(Cryptographically Secure Pseudo-Random Number Generator – CSPRNG)來生成。CSPRNG和普通的隨機數生成器有很大不同,如C語言中的rand()函式。物如其名,CSPRNG專門被設計成用於加密,它能提供高度隨機和無法預測的隨機數。我們顯然不希望自己的鹽值被猜測到,所以一定要使用CSPRNG。下面的表格列出了當前主流程式語言中的CSPRNG方法:
對於每個使用者的每個密碼,鹽值都應該是獨一無二的。每當有新使用者註冊或者修改密碼,都應該使用新的鹽值進行加密。並且這個鹽值也應該足夠長,使得有足夠多的鹽值以供加密。一個好的標準的是:鹽值至少和雜湊函式的輸出一樣長;鹽值應該被儲存和密碼雜湊一起儲存在賬戶資料表中。
③ 加鹽之後儲存密碼的步驟
a.使用CSPRNG生成一個長度足夠的鹽值;
b.將鹽值混入密碼,並使用標準的加密雜湊函式進行加密,如SHA256;
c.把雜湊值和鹽值一起存入資料庫中對應此使用者的那條記錄。
④ 加鹽之後校驗密碼的步驟
a.從資料庫取出使用者的密碼雜湊值和對應鹽值;
b.將鹽值混入使用者輸入的密碼,並且使用同樣的雜湊函式進行加密;
c.比較上一步的結果和資料庫儲存的雜湊值是否相同,如果相同那麼密碼正確,反之密碼錯誤。
⑤ 在客戶端加鹽還是在服務端加鹽?
如果你正在開發一個Web程式,你可能會疑惑到底在哪進行加密。是使用JavaScript在使用者的瀏覽器上操作呢,還是將密碼“裸體”傳送到伺服器再進行加密?
即使瀏覽器端用JavaScript加密了,你仍然需要在服務端再次進行加密。試想有個網站在瀏覽器將密碼經過雜湊後傳送到伺服器,那麼在認證使用者的時候,網站收到雜湊值和資料庫中的值進行比對就可以了。這看起來比只在伺服器端加密安全得多,因為至始至終沒有將使用者的密碼明文傳輸,但實際上不是這樣。
問題在於,從客戶端來看,經過雜湊的密碼邏輯上成為使用者真正的密碼。為了通過伺服器認證,使用者只需要傳送密碼的雜湊值即可。如果有壞小子獲取了這個雜湊值,他甚至可以在不知道使用者密碼的情況通過認證。更進一步,如果他用某種手段入侵了網站的資料庫,那麼不需要去猜解任何人的密碼,就可以隨意使用每個人的帳號登入。
這並不是說你不應該在瀏覽器端進行加密,但是如果你這麼做了,一定要在服務端再次加密。在瀏覽器中進行雜湊加密是個好想法,不過實現的時候注意下面幾點:
• 客戶端密碼雜湊並不能代替HTTPS(SSL/TLS)。如果瀏覽器和伺服器之間的連線是不安全的,那麼中間人攻擊可以修改JavaScript程式碼,刪除加密函式,從而獲取使用者密碼。
• 有些瀏覽器不支援JavaScript,也有的使用者禁用了瀏覽器的JavaScript功能。為了最好的相容性,你的程式應該檢測JavaScript是否可用,如果答案為否,需要在服務端模擬客戶端的加密。
• 客戶端雜湊同樣需要加鹽,很顯然的辦法就是向伺服器請求使用者的鹽值,但是不要這麼做。因為這給了壞蛋一個機會,能夠在不知道密碼的情況下檢測使用者名稱是否有效。既然你已經在服務端對密碼進行了加鹽雜湊,那麼在客戶端把使用者名稱(或郵箱)加上網站特有的字串(如域名)作為鹽值是可行的。