SHA與PBKDF2加密的實現與應用
雜湊演算法並不是一種加密演算法。不能對使用者資訊的保護。因為雜湊演算法是單向的,可以將任何大小的資料轉化為定長的“指紋”,而且無法被反向計算。
但Hash演算法常用於對口令的儲存上。另外,即使資料來源只改動了一丁點,雜湊的結果也會完全不同。這樣的特性使得它非常適合用於儲存密碼,因為我們需要加密後的密碼無法被解密,同時也能保證正確校驗每個使用者的密碼。
例如,使用者登入網站需要通過使用者名稱和密碼來進行驗證。如果網站後臺直接儲存使用者的口令明文,一旦資料庫發生洩漏,後果不堪設想。
然而,由於有時候使用者設定的口令的強度不夠,只是一些常見的字串,如password、123456等。有人專門蒐集這些常見的口令,計算對應的Hash值,製成字典。這樣通過Hash值可以快速的查到原始口令。這一型別以空間換時間的攻擊方法包括字典攻擊和彩虹攻擊(只儲存一條Hash鏈的首尾值,相對字典攻擊可以節省儲存空間)等。
為了防範這一類攻擊,一般採用加鹽值(salt)的方法。這樣儲存的不是口令明文的Hash值,而是口令再加上一段隨機字串(即“鹽”)之後的Hash值。鹽是一個新增到使用者的密碼雜湊過程中的一段隨機序列。這個機制能夠防止通過預先計算結果的彩虹表破解。
每個使用者都有自己的鹽,這樣的結果就是即使使用者的密碼相同,通過加鹽後雜湊值也將不同。
為了校驗密碼是否正確,我們需要儲存鹽值。通常和密碼雜湊值一起存放在賬戶資料庫中,或者直接存為雜湊字串的一部分。
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 { /** * MD5和SHA1已被破解,SHA-1已被證明不具備“強抗碰撞性” * 一般推薦至少使用SHA2-256或更安全的演算法。 * PBKDF2WithHmacSHA256 * PBKDF2WithHmacSHA384 * PBKDF2WithHmacSHA512 */ public static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA256"; /** * 鹽的長度 */ public static final int SALT_BYTE_SIZE = 32 / 2; /** * 生成密文的長度 */ public static final int HASH_BIT_SIZE = 128 * 4; /** * 迭代次數 */ public static final int PBKDF2_ITERATIONS = 1000; /** * 對輸入的密碼進行驗證 * * @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 the hex string * @return the hex string decoded into a byte array */ 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 the byte array to convert * @return a length*2 character string encoding the byte array */ 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; } public static void main(String[] args) { String password = "test"; 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.length()); System.out.println(salt + " " + salt.length()); System.out.println(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"); } } }
--------------------------程式碼實現來載地址--------------------------------------
作者:阿牛哞了一聲
原文:https://blog.csdn.net/u014375869/article/details/46773995
----------------------------------------------------------------------------------------