為什么持久化的時(shí)候要加密
密碼肯定是不能明文存儲(chǔ)的捐友,要不然數(shù)據(jù)庫被攻破转锈,就可以獲得大批對(duì)應(yīng)個(gè)人信息的密碼,畢竟很多人就那幾個(gè)密碼楚殿,拿著個(gè)人信息和密碼去試試沒準(zhǔn)就破解了了一大堆賬號(hào)撮慨。再者密碼明文存儲(chǔ),萬一有個(gè)搞 事情的員工脆粥,可以輕易的獲取客戶的密碼信息砌溺,很不安全的。更多危害請(qǐng)自行百度变隔,總之就是不能明文存儲(chǔ)规伐。
什么是哈希算法
簡單的說哈希算法是一個(gè)多對(duì)一的映射函數(shù),他有兩個(gè)重要的性質(zhì):不可逆和無沖突匣缘。
因?yàn)槭且粋€(gè)多對(duì)一映射猖闪,可以由多個(gè)x推出y,但是不能由y推出他是來源于哪個(gè)x肌厨,這就是不可逆培慌。
無沖突是值當(dāng)你知道一個(gè)x可以推出y,你無法推出另一個(gè)可以推出y的x柑爸。
不可逆意味著不可能獲取明文信息吵护,無沖突意味著不可能破解加密算法。這兩個(gè)理論上都是不能實(shí)現(xiàn)的,但是如果正向計(jì)算很容易馅而,逆向計(jì)算窮盡計(jì)算資源也做不到就認(rèn)為已經(jīng)實(shí)現(xiàn)了不可逆和無沖突祥诽。所以哈希算法是一種不可逆的非對(duì)稱加密算法。
使用加鹽哈希加密密碼基本思路
鹽值來源于英文salt,應(yīng)該是佐料差不多的意思瓮恭,加鹽哈希的基本思路就是生成一堆隨機(jī)字符作為salt值混入要加密的數(shù)據(jù)中雄坪,然后進(jìn)行多次哈希獲取最終的哈希值,并將哈希值和salt保存到數(shù)據(jù)庫等實(shí)現(xiàn)持久化屯蹦。驗(yàn)證數(shù)據(jù)的時(shí)候使用相同的salt值以同樣的算法插入并進(jìn)行哈希計(jì)算出新的哈希值與查出來的哈希值進(jìn)行對(duì)比是否一致诸衔。
這里需要保存哈希值和salt值兩個(gè)值,也可以簡單直接將salt插入哈希值中存儲(chǔ)颇玷,解密的時(shí)候?qū)?duì)應(yīng)位置的值截取出來作為salt笨农。
java代碼實(shí)現(xiàn):
maven導(dǎo)包:
<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";
//安全隨機(jī)函數(shù)
private static SecureRandom random = new SecureRandom();
//哈希次數(shù)
public static final int HASH_INTERATIONS = 1024;
/**
* Hex編碼.byte[]轉(zhuǎn)為數(shù)據(jù)庫可以存取的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);
}
}
/**
* 生成隨機(jī)的Byte[]作為salt.
*
* @param numBytes byte數(shù)組的大小
*/
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 哈希次數(shù)
* @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);
}
/**
* 對(duì)比
* @param plainPassword(明文密碼)
* 與
* @param encryptedPassword(密文密碼)
*/
public static boolean comparePassword(String plainPassword,String encryptedPassword) {
byte[] salt = decodeHex(encryptedPassword.substring(0, 32));//一個(gè)英文字符倆字節(jié)帖渠,截取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));
}
}
執(zhí)行結(jié)果
77a8063988c6f69e661f5a256e8a72670442cfc26be36ed98dca1788529d26a05cd428ba1142a7d7448e95f5dd7b71e2
true
false