为什么持久化的时候要加密
密码肯定是不能明文存储的,要不然数据库被攻破,就可以获得大批对应个人信息的密码,毕竟很多人就那几个密码,拿着个人信息和密码去试试没准就破解了了一大堆账号。再者密码明文存储,万一有个搞 事情的员工,可以轻易的获取客户的密码信息,很不安全的。更多危害请自行百度,总之就是不能明文存储。
什么是哈希算法
简单的说哈希算法是一个多对一的映射函数,他有两个重要的性质:不可逆和无冲突。
因为是一个多对一映射,可以由多个x推出y,但是不能由y推出他是来源于哪个x,这就是不可逆。
无冲突是值当你知道一个x可以推出y,你无法推出另一个可以推出y的x。
不可逆意味着不可能获取明文信息,无冲突意味着不可能破解加密算法。这两个理论上都是不能实现的,但是如果正向计算很容易,逆向计算穷尽计算资源也做不到就认为已经实现了不可逆和无冲突。所以哈希算法是一种不可逆的非对称加密算法。
使用加盐哈希加密密码基本思路
盐值来源于英文salt,应该是佐料差不多的意思,加盐哈希的基本思路就是生成一堆随机字符作为salt值混入要加密的数据中,然后进行多次哈希获取最终的哈希值,并将哈希值和salt保存到数据库等实现持久化。验证数据的时候使用相同的salt值以同样的算法插入并进行哈希计算出新的哈希值与查出来的哈希值进行对比是否一致。
这里需要保存哈希值和salt值两个值,也可以简单直接将salt插入哈希值中存储,解密的时候将对应位置的值截取出来作为salt。
java代码实现:
maven导包:
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.12</version>
</dependency>
核心代码:
package lixingchen.FlexBlog.common.utils;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringEscapeUtils;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Base64;
public class SecurityUtils {
//捣乱的盐值长度
public static final int SALT_SIZE = 16;
//采用的哈希算法
private static final String SHA256 = "SHA-256";
//安全随机函数
private static SecureRandom random = new SecureRandom();
//哈希次数
public static final int HASH_INTERATIONS = 1024;
/**
* Hex编码.byte[]转为数据库可以存取的String
*/
public static String encodeHex(byte[] input) {
return new String(Hex.encodeHex(input));
}
/**
* Hex解码.
*/
public static byte[] decodeHex(String input) {
try {
return Hex.decodeHex(input.toCharArray());
} catch (DecoderException e) {
throw new RuntimeException(e);
}
}
/**
* 生成随机的Byte[]作为salt.
*
* @param numBytes byte数组的大小
*/
private static byte[] generateSalt(int numBytes) {
byte[] bytes = new byte[numBytes];
random.nextBytes(bytes);
return bytes;
}
/**
*
* @param input
* @param algorithm hash算法
* @param salt
* @param iterations 哈希次数
* @return
*/
private static byte[] digest(byte[] input, String algorithm, byte[] salt,int iterations) {
try {
MessageDigest digest = MessageDigest.getInstance(algorithm);
if (salt != null) {
digest.update(salt);
}
byte[] result = digest.digest(input);
for (int i = 1; i < iterations; i++) {
digest.reset();
result = digest.digest(result);
}
return result;
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
/**
* 传入明文密码,生成密文密码
*/
public static String entryptPassword(String plainPassword) {
String plain =StringEscapeUtils.unescapeHtml(plainPassword);
byte[] salt = generateSalt(SALT_SIZE);
byte[] hashPassword = digest(plain.getBytes(),SHA256, salt, HASH_INTERATIONS);
return encodeHex(salt) + encodeHex(hashPassword);
}
/**
* 对比
* @param plainPassword(明文密码)
* 与
* @param encryptedPassword(密文密码)
*/
public static boolean comparePassword(String plainPassword,String encryptedPassword) {
byte[] salt = decodeHex(encryptedPassword.substring(0, 32));//一个英文字符俩字节,截取16位盐值
String plain = StringEscapeUtils.unescapeHtml(plainPassword);
byte[] hashPassword = digest(plain.getBytes(),SHA256, salt, HASH_INTERATIONS);
return encodeHex(hashPassword).equals(encryptedPassword.substring(32));
}
/**
* @param args
*/
public static void main(String[] args)throws Exception {
String plainPassword = "123456中文qwetyu";
String encryptedPassword = entryptPassword(plainPassword);
System.out.println(encryptedPassword);
System.out.println(comparePassword(plainPassword,encryptedPassword));
System.out.println(comparePassword("wrong_password",encryptedPassword));
}
}
执行结果
77a8063988c6f69e661f5a256e8a72670442cfc26be36ed98dca1788529d26a05cd428ba1142a7d7448e95f5dd7b71e2
true
false