PBKDF2 java加密与解密
public class suanfa { public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException { String originalPassword = "123456"; String generatedSecuredPasswordHash = generateStorngPasswordHash(originalPassword); System.out.println(generatedSecuredPasswordHash); boolean matched = validatePassword("123456", generatedSecuredPasswordHash); System.out.println(matched); matched = validatePassword("password1", generatedSecuredPasswordHash); System.out.println(matched); } private static boolean validatePassword(String originalPassword, String storedPassword) throws NoSuchAlgorithmException, InvalidKeySpecException { String[] parts = storedPassword.split(":"); int iterations = Integer.parseInt(parts[0]); byte[] salt = fromHex(parts[1]); byte[] hash = fromHex(parts[2]); PBEKeySpec spec = new PBEKeySpec(originalPassword.toCharArray(), salt, iterations, hash.length * 8); SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); byte[] testHash = skf.generateSecret(spec).getEncoded(); int diff = hash.length ^ testHash.length; for(int i = 0; i < hash.length && i < testHash.length; i++) { diff |= hash[i] ^ testHash[i]; } return diff == 0; } private static byte[] fromHex(String hex) throws NoSuchAlgorithmException { byte[] bytes = new byte[hex.length() / 2]; for(int i = 0; i<bytes.length ;i++) { bytes[i] = (byte)Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16); } return bytes; } private static String generateStorngPasswordHash(String password) throws NoSuchAlgorithmException, InvalidKeySpecException { int iterations = 147852; char[] chars = password.toCharArray(); byte[] salt = getSalt().getBytes(); PBEKeySpec spec = new PBEKeySpec(chars, salt, iterations, 64 * 8); SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); byte[] hash = skf.generateSecret(spec).getEncoded(); return iterations + ":" + toHex(salt) + ":" + toHex(hash); } private static String getSalt() throws NoSuchAlgorithmException { SecureRandom sr = SecureRandom.getInstance("SHA1PRNG"); byte[] salt = new byte[16]; sr.nextBytes(salt); return salt.toString(); } private static String toHex(byte[] array) throws NoSuchAlgorithmException { 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; } } }
较高密码安全实现使用 PBKDF2WithHmacSHA1 算法
到目前为止,我们已经了解如何为密码生成安全的 Hash 值以及通过利用 salt 来加强它的安全性。但今天的问题是,硬件的速度已经远远超过任何使用字典或彩虹表进行的暴力攻击,并且任何密码都能被破解,只是使用时间多少的问题。
为了解决这个问题,主要想法是尽可能降低暴力攻击速度来保证最小化的损失。我们下一个算法同样是基于这个概念。目标是使 Hash 函数足够慢以妨碍攻击,并对用户来说仍然非常快且不会感到有明显的延时。
要达到这个目的通常是使用某些 CPU 密集型算法来实现,比如 PBKDF2, Bcrypt 或 Scrypt 。这些算法采用 work factor(也称之为 security factor)或迭代次数作为参数来确定 Hash 函数将变的有多慢,并且随着日后计算能力的提高,可以逐步增大 work factor 来使之与计算能力达到平衡。
更加安全的密码实现使用 bcrypt 和 scrypt 算法
bcrypt 的背后的思想与 PBKDF2 类似。只是 Java 中并没有内置支持使攻击者变慢的 bcrypt 算法实现,但你仍然可以找到并下载它的源码。
//加盐 可变的 随机的 长度不能过短
最后说明
- 在应用程序中存储密码明文是极其危险的事情。
- MD5 提供了最基本的安全 Hash 生成,使用时应为其添加 slat 来进一步加强它的安全性。
- MD5 生成128位的 Hash。为了使它更安全,应该使用 SHA 算法生成 160-bit 或 512-bit 的长 Hash,其中 512-bit 是最强的。
- 虽然使用 SHA Hash 密码也能被当今快速的硬件破解,如要避免这一点,你需要的算法是能让暴力攻击尽可能的变慢且使影响减至最低。这时候你可以使用 PBKDF2, BCrypt 或 SCrypt 算法。
- 请在深思熟虑后来选择适当的安全算法。
参考资料:
- https://en.wikipedia.org/wiki/MD5
- https://en.wikipedia.org/wiki/Secure_Hash_Algorithm
- http://en.wikipedia.org/wiki/Bcrypt
- http://en.wikipedia.org/wiki/Scrypt
- http://en.wikipedia.org/wiki/PBKDF2
- https://github.com/wg/scrypt
- http://www.mindrot.org/projects/jBCrypt/