做過網站的人都知道用戶密碼必須經過加密的,其中用的最普遍的就是MD5加密了.但是隨着彩虹橋技術的興起,MD5加密已經不再安全.
如對於MD5加密來說攻擊者只需要一個簡單的sql語句`:select * from userInfo where password=’4QrcOUm6Wau+VuBX8g+IPg==’` 就可以知道有幾個用戶密碼是”123456”,這對一個項目來說十分危險。
所以一般在加密之前,配上一個一串的隨機序列。稱之為salt。
PBKDF2(Password-Based Key Derivation Function)。
PBKDF2算法通過多次hash來對密碼進行加密。原理是通過password和salt進行hash,然后將結果作為salt在與password進行hash,多次重復此過程,生成最終的密文。此過程可能達到上千次,逆向破解的難度太大,破解一個密碼的時間可能需要幾百年,所以PBKDF2算法是安全的.
密碼加鹽。鹽是一個添加到用戶的密碼哈希過程中的一段隨機序列。這個機制能夠防止通過預先計算結果的彩虹表破解。每個用戶都有自己的鹽,這樣的結果就是即使用戶的密碼相同,通過加鹽后哈希值也將不同。為了校驗密碼是否正確,我們需要儲存鹽值。通常和密碼哈希值一起存放在賬戶數據庫中,或者直接存為哈希字符串的一部分。
PasswordEncryption工具類如下

package com.xianquan.web.util; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import java.math.BigInteger; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.spec.InvalidKeySpecException; import java.security.spec.KeySpec; public class PasswordEncryption { public static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1"; /** * 鹽的長度 */ public static final int SALT_BYTE_SIZE = 32 / 2; /** * 生成密文的長度 */ public static final int HASH_BIT_SIZE = 128 * 4; /** * 迭代次數 */ public static final int PBKDF2_ITERATIONS = 10000; /** * 對輸入的密碼進行驗證 * * @param attemptedPassword 待驗證的密碼 * @param encryptedPassword 密文 * @param salt 鹽值 * @return 是否驗證成功 * @throws NoSuchAlgorithmException * @throws InvalidKeySpecException */ public static boolean authenticate(String attemptedPassword, String encryptedPassword, String salt) throws NoSuchAlgorithmException, InvalidKeySpecException { // 用相同的鹽值對用戶輸入的密碼進行加密 String encryptedAttemptedPassword = getEncryptedPassword(attemptedPassword, salt); // 把加密后的密文和原密文進行比較,相同則驗證成功,否則失敗 return encryptedAttemptedPassword.equals(encryptedPassword); } /** * 生成密文 * * @param password 明文密碼 * @param salt 鹽值 * @return * @throws NoSuchAlgorithmException * @throws InvalidKeySpecException */ public static String getEncryptedPassword(String password, String salt) throws NoSuchAlgorithmException, InvalidKeySpecException { KeySpec spec = new PBEKeySpec(password.toCharArray(), fromHex(salt), PBKDF2_ITERATIONS, HASH_BIT_SIZE); SecretKeyFactory f = SecretKeyFactory.getInstance(PBKDF2_ALGORITHM); return toHex(f.generateSecret(spec).getEncoded()); } /** * 通過提供加密的強隨機數生成器 生成鹽 * * @return * @throws NoSuchAlgorithmException */ public static String generateSalt() throws NoSuchAlgorithmException { SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); byte[] salt = new byte[SALT_BYTE_SIZE]; random.nextBytes(salt); return toHex(salt); } /** * 十六進制字符串轉二進制字符串 * * @param hex * @return */ private static byte[] fromHex(String hex) { byte[] binary = new byte[hex.length() / 2]; for (int i = 0; i < binary.length; i++) { binary[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16); } return binary; } /** * 二進制字符串轉十六進制字符串 * * @param array * @return */ private static String toHex(byte[] array) { BigInteger bi = new BigInteger(1, array); String hex = bi.toString(16); int paddingLength = (array.length * 2) - hex.length(); if (paddingLength > 0){ return String.format("%0" + paddingLength + "d", 0) + hex; } else { return hex; } } }
生成密文:首先要生成一個鹽值salt,再把原始密碼和salt加密得到密文。
驗證密文:把用戶輸入的密碼和同樣的鹽值salt使用相同的加密算法得到一個密文,將這個密文和原密文相比較,相同則驗證通過,反之則不通過。
調用實例如下

public static void main(String[] args) { String password = "admin"; String salt; String ciphertext; try { salt = PasswordEncryption.generateSalt(); ciphertext = PasswordEncryption.getEncryptedPassword(password, salt); boolean result = PasswordEncryption.authenticate(password, ciphertext, salt); System.out.println("password:"+ password + " " + password.length()); System.out.println("salt" + salt + " " + salt.length()); System.out.println("ciphertext" + ciphertext + " " + ciphertext.length()); if (result) { System.out.println("succeed"); } else { System.out.println("failed"); } } catch (NoSuchAlgorithmException e) { System.out.println("NoSuchAlgorithmException"); } catch (InvalidKeySpecException e) { System.out.println("InvalidKeySpecException"); } }