網絡安全問題日益嚴重,即使是大型知名企業也面臨敏感用戶數據泄露的問題。這些問題可能包括對數據庫的未經授權的訪問以及日志的泄露等等。此外,我們也經常遇到零日漏洞攻擊(Zero-Day Vulnerabilities),所有這些都對用戶自身安全和企業聲譽產生了負面影響。本文將介紹如何使用密碼認證來實現用戶認證的數據存儲。
一、身份驗證
身份驗證是用戶確認他是所提供標識符的所有者的過程。最明顯和人們最熟悉的身份驗證過程是密碼身份驗證。用戶進入登錄頁面,輸入用戶名和密碼,然后登錄。下文將展示如何在服務器上實現身份驗證。
認證過程可以用一張圖表示:
服務端收到請求后,服務器將使用存儲在數據庫中的值(在注冊期間保存的)檢查用戶的數據,并判斷用戶是否可以通過身份驗證。如果檢查成功,通常會在服務器上創建一個會話,并將其標識符作為 Cookie 在響應中返回。
那么,用戶注冊時如何保存認證數據呢?
1.將密碼存儲為純文本
在這種情況下,數據庫中的數據將作為開放數據存儲。任何有權訪問數據庫的人都可以獲取用戶的密碼,比如數據庫管理員、支持人員或開發人員。此外,系統中始終存在漏洞風險,可能允許入侵者訪問數據庫且進行下載和轉存。
理想情況下,每項服務都應有其唯一的密碼,這樣就可以避免在服務中泄露身份驗證數據的風險。但由于我們使用的服務如此之多,記住所有密碼是不可能的。一種解決方案是密碼管理器,但使用的人很少,并且用戶傾向于能隨處使用的一個或多個密碼。當一項服務的數據泄露,使用該密碼的其他服務也會受到影響,因此強烈建議不要以純文本形式保存密碼,從而保護用戶免受此類問題影響。
2.密碼哈希
哈希算法是根據用戶密碼計算數字摘要的特定函數。該函數的工作原理是可以足夠快地從密碼中獲取哈希值,而無法在足夠的時間內完成反向轉換。哈希函數有MD5、SHA-1、SHA-256 、SH3-512等。使用這些函數,我們保存到數據庫中的不是密碼本身,而是使用哈希函數從密碼中計算出來的數值摘要值。例如,在 Java 中,使用如下所示操作獲取密碼的哈希值:
6a158d9847a80e99511b2a7866233e404b305fdb7c953a30deb65300a57a0655
- 1.
這個變體已經好很多了,但它仍有缺點。其中之一是具有相同密碼的用戶將具有相同的哈希值。如果入侵者獲得對數據庫的訪問權,他就可以根據自己的目的使用數據,同時暴力破解密碼的可能性也相當危險。你可以使用流行的密碼和哈希來創建數據庫(或使用現有數據庫),因此可以快速恢復用戶密碼的值。這也是不推薦這一選項的原因。
3.使用唯一鹽(Salt)的密碼哈希
針對前一個解決方案的痛點,我們可以使用每個用戶唯一的鹽。鹽是與密碼連接的隨機值,并從結果中獲取哈希函數。
Random random = new SecureRandom();
byte[] saltBytes = new byte[16];
random.nextBytes(saltBytes);
String salt = new BigInteger(1, saltBytes).toString(16);
- 1.
- 2.
- 3.
- 4.
- 5.
這樣,我們一次解決了幾個問題。首先,具有相同密碼的用戶將具有不同的鹽值,因此哈希函數的值也不同。因為無法應用預先計算的哈希表,入侵者獲取密碼將更加困難。
4.特殊算法 PBKDF2、BCrypt、SCrypt
最好的選擇是使用為散列密碼開發的特殊算法。這些算法是自適應的,可以有意讓計算時間放慢,以使暴力攻擊更加困難。
我們以 BCrypt 算法(實現是 Spring Security 的一部分)為例:
$2a$10$alXdzX7lkEp52xiKS7YfuelpoFqz6AsvyBwIEz/BbWghdkmwGqYoy
$2a$ - the hash algorithm identifier
10 - number of hashing rounds (2^10 = 1024)
alXdzX7lkEp52xiKS7Yfue - salt
lpoFqz6AsvyBwIEz/BbWghdkmwGqYoy - hash
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
為了計算這個函數,我們使用了1024輪哈希。隨著時間的推移和計算能力的增長,我們可以增加這個值來保持計算的復雜性。
在對用戶進行身份驗證時,只需調用檢查發送的密碼的方法以及存儲在數據庫中的哈希值:
x = H(salt, password)
v = g^x (mod N)
- 1.
- 2.
H- 哈希函數(SHA-1、SHA256 等)。
g, N- 可以從RFC5054.Appendix A中選擇的常量。需要注意的是,選擇的常量和哈希函數在服務器和客戶端上必須相同。
salt 和verifier 值可以在客戶端和服務器上計算。如果這些值是在客戶端計算的,我們根本不會在通信通道上傳輸密碼,但我們也無法檢查服務器上的密碼策略(長度、通配符數量等)。因此,這些檢查也需要傳輸到客戶端。
例如,你可以使用Nimbus SRP 庫:
salt: 6bb9db1c839bdc59ecbcd0ee12488462
verifier: f28aed4372b1312ccdd6e281c7270be503bac99bff845c41da8189eadf9e4497
- 1.
- 2.
這些值必須保存在數據庫中,并在以后的客戶端身份驗證過程中使用。該協議最大的優點是密碼不會以任何方式傳輸到服務器,并且無法從verifier值中恢復原始密碼。此外,verifier僅在注冊期間傳輸(如果在客戶端計算)并且僅用于身份驗證期間的計算。該協議本身可以抵抗 MITM 攻擊,這意味著如果有人意外啟用了服務器上所有用戶請求的日志記錄,并且這些日志隨后被泄露,密碼也不會泄露。此數據在每個會話中計算,不能用于重新輸入。
二、結論
正確使用現代用戶身份驗證方法可以大大降低身份驗證數據泄露的可能,但身份驗證只是網絡安全領域的一個側面。除此之外,日志請求和日志存儲也是值得人們關注的問題。
原文鏈接:https://dzone.com/articles/password-authentication-how-to-do-it-correctly
譯者介紹
baron,51CTO社區編輯,具有九年手機安全/SOC底層安全開發經驗,擅長TrustZone/TEE安全產品的設計和開發。