作為軟體開發者,其中最重要的一個責任就是保護使用者的個人資訊,如果使用者沒有相關技術知識,他們在使用我們服務的時候,別無選擇只能信任我們。可惜的是,當我們調查關於密碼處理的時候,我們發現有各種不同的處理方式,而這些方式有很多都不安全。雖然不可能構建一個完全安全的系統,但我們可以通過一些簡單的步驟讓我們的密碼儲存足夠安全。
不應該:
首先讓我們看看當我們構建一個需要使用者認證的系統的時候不應該怎麼做。
●在不得已的時候不要自己儲存使用者的認證資訊。我們可以考慮使用OAuth的提供者例如Google、Facebook。如果構建企業內部的應用,可以考慮使用已有的內部認證服務,例如企業LDAP或者Kerberos服務。無論是面向公眾的還是面向內部的應用程式,使用者會喜歡這個應用,因為他不需要多記住一個ID和密碼,同時也少了受黑客攻擊的危險。
●如果你必須儲存認證資訊,不要儲存明文密碼。這句話就不解釋了。
●不要使用可逆的加密方式,除非你在某種狀況下真的需要查出來明文密碼。因為在進行使用者身份驗證的時候並不需要明文密碼去比對。
●不要使用過時的雜湊演算法,例如md5,在現在這個社會,有人可以通過構建一個超大的md5庫來反向的查詢出明文。換句話說md5雜湊基本上沒什麼用,你要是不相信可以拿這個密文(569a70c2ccd0ac41c9d1637afe8cd932)去 http://www.md5hacker.com/ 上看看,幾秒內就可以查出明文了。
應該:
說完了不應該做的,就說說應該做的:
●選擇一個單向(不可逆)的加密演算法。就像我上面說的一樣,僅僅儲存加密後的使用者密碼,使用者每次認證就使用相同的演算法加密後比對就可以了。
●選擇一個你的應用可以承受的最慢的加密演算法。任何現代的加密演算法都支援在加密的時候接受引數從而使加密時間延長,而解密也自然就更難。(例如PBKDF2,可以通過制定迭代的次數來實現)。為什麼慢了好呢?因為使用者幾乎不會關心他為了認證自己的賬戶額外的花銷了100ms。但是黑客就不同了,當他進行上10億次的嘗試計算的時候,就有他喝一壺的了。
●選擇一個流行的演算法。美國國家標準與技術研究院推薦使用PBKDF2加密密碼。
PBKDF2
在我給出示例程式碼前,讓我們先來看看PBKDF2演算法。
●美國國家標準與技術研究院推薦。
●可以通過調整key來擴充套件,從而避免暴力破解。通過key擴充套件的基本思路是,在將密碼雜湊後,再使用key加上雜湊值再使用相同的演算法進行多次的雜湊。如果黑客嘗試去破解的話,他會因此多花費幾十億次計算的時間。前面提到過,越慢越好,PBKDF2可以通過指定迭代次數,你想讓他多慢,他就有多慢。
●通過加鹽的方式預防彩虹表的破解方式。鹽是一個新增到使用者的密碼雜湊過程中的一段隨機序列。這個機制能夠防止通過預先計算結果的彩虹表破解。每個使用者都有自己的鹽,這樣的結果就是即使使用者的密碼相同,通過加鹽後雜湊值也將不同。然而,在將鹽與密文儲存的位置上有很多矛盾的地方,有的時候將兩者存在一起比較方便,有的時候為了安全考慮又不得不將兩者分開儲存。由於PBKDF2演算法通過key的機制避免了暴力破解,我覺得沒必要將鹽隱藏起來,就跟密文儲存在同一個位置。
●不需要額外的庫或者工具,這是一個開源的實現,在工作環境中能很方便的使用。
最後讓我們來看一個例子
這裡使用PBKDF2加密的Java程式碼,僅僅依賴Java SE 6.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.spec.InvalidKeySpecException; import java.security.spec.KeySpec; import java.util.Arrays; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; public class PasswordEncryptionService { public boolean authenticate(String attemptedPassword, byte[] encryptedPassword, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException { // Encrypt the clear-text password using the same salt that was used to // encrypt the original password byte[] encryptedAttemptedPassword = getEncryptedPassword(attemptedPassword, salt); // Authentication succeeds if encrypted password that the user entered // is equal to the stored hash return Arrays.equals(encryptedPassword, encryptedAttemptedPassword); } public byte[] getEncryptedPassword(String password, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException { // PBKDF2 with SHA-1 as the hashing algorithm. Note that the NIST // specifically names SHA-1 as an acceptable hashing algorithm for PBKDF2 String algorithm = "PBKDF2WithHmacSHA1"; // SHA-1 generates 160 bit hashes, so that's what makes sense here int derivedKeyLength = 160; // Pick an iteration count that works for you. The NIST recommends at // least 1,000 iterations: // http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf // iOS 4.x reportedly uses 10,000: // http://blog.crackpassword.com/2010/09/smartphone-forensics-cracking-blackberry-backup-passwords/ int iterations = 20000; KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterations, derivedKeyLength); SecretKeyFactory f = SecretKeyFactory.getInstance(algorithm); return f.generateSecret(spec).getEncoded(); } public byte[] generateSalt() throws NoSuchAlgorithmException { // VERY important to use SecureRandom instead of just Random SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); // Generate a 8 byte (64 bit) salt as recommended by RSA PKCS5 byte[] salt = new byte[8]; random.nextBytes(salt); return salt; } } |
流程是這樣:
1、當增加一個使用者的時候,呼叫generateSalt()生成鹽,然後呼叫getEncryptedPassword(),同時儲存鹽和密文。再次強調,不要儲存明文密碼,不要儲存明文密碼,因為沒必要!不要擔心將鹽和密文儲存在同一張表中,上面已經說過了,這個無關緊要。
2、當認證使用者的時候,從資料庫中取出鹽和密文,將他們和明文密碼同時傳給authenticate(),根據返回結果判斷是否認證成功。
3、當使用者修改密碼的時候,仍然可以使用原來的鹽,只需要呼叫getEncryptedPassword()方法重新生成密文就可以了。
參考文件:
●NIST:Special Publication 800-132
●維基百科:PBKDF2
●維基百科: Salt (cryptography)
●維基百科: Rainbow Table Attacks