做过网站的人都知道用户密码必须经过加密的,其中用的最普遍的就是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"); } }