【密碼學】輕松理解“加鹽”的原理與java實現


轉自:https://blog.csdn.net/DavidHuang2017/article/details/80283469

一、什么是加鹽?

1.背景

        現在很多公司后台以hash值形式存儲用戶密碼(雖然本文以MD5哈希函數為例,但becrypt函數最常用的),用於哈希函數存在碰撞的特性,當后台數據庫被攻擊然后獲取到用戶密碼哈希值時,還是能通過一定的方法(比如彩虹表攻擊)破解用戶密碼。

舉個例子:http://www.cmd5.com/

 

能破解。

2.加鹽原理簡介

        簡單來說:由原來的H(p)變成了H(p+salt),相當於哈希函數H發生了變化,每次哈希計算使用的salt是隨機的

        H如果發生了改變,則已有的彩虹表數據就完全無法使用,必須針對特定的H重新生成,這樣就提高了破解的難度。

二、如何加鹽?

        如何加鹽,不同的哈希算法不同的公司不盡相同但思路都是差不多的。本文以MD5的一個簡單加鹽處理為例,講解加鹽的java實現:

1.生成鹽

private static char[] hex = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

/**
* @Author: DavidHuang
* @Time: 2018/5/10 21:14
* @return: salt
* @params:
* @Descrption: 自定義簡單生成鹽,是一個隨機生成的長度為16的字符串,每一個字符是隨機的十六進制字符
*/
public static String salt() {
Random random = new Random();
StringBuilder sb = new StringBuilder(16);
for (int i = 0; i < sb.capacity(); i++) {
sb.append(hex[random.nextInt(16)]);
}
return sb.toString();
}
        這只是一個生成鹽的想法而已,你可以按自己的想法來,只要保證每次執行生成的鹽隨機即可。

2.輸入加鹽

String inputWithSalt = inputStr + salt;//加鹽,輸入加鹽
       加鹽非常簡單吧

3.輸出帶鹽

        輸出帶鹽是我自己取的一個名字而已,這個過程可選不要。實際上是將這次哈希計算過程用到的salt存儲到這次hash值中,用於后面進行驗證密碼時進行hash計算,即注冊存儲密碼時和登陸驗證密碼時用到的salt要一樣,免除了另存hash操作。

/**
*@Author: DavidHuang
*@Time: 2018/5/11 14:47
*@return:
*@params: [inputStr, type] inputStr是輸入的明文;type是處理類型,0表示注冊存hash值到庫時,1表示登錄驗證時
*@Descrption: MD5加鹽,鹽的獲取分兩種情況;輸入明文加鹽;輸出密文帶鹽(將salt存儲到hash值中)
*/
public static String MD5WithSalt(String inputStr, int type) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");//申明使用MD5算法,更改參數為"SHA"就是SHA算法了

String salt = null;
if (type == 0) {//注冊存hash值到庫時,new salt
salt = salt();
} else if (type == 1) {//登錄驗證時,使用從庫中查找到的hash值提取出的salt
String queriedHash=null;//從庫中查找到的hash值
salt=getSaltFromHash(queriedHash);
}

String inputWithSalt = inputStr + salt;//加鹽,輸入加鹽
String hashResult = byte2HexStr(md.digest(inputWithSalt.getBytes()));//哈希計算,轉換輸出
System.out.println("加鹽密文:"+hashResult);

/*將salt存儲到hash值中,用於登陸驗證密碼時使用相同的鹽*/
char[] cs = new char[48];
for (int i = 0; i < 48; i += 3) {
cs[i] = hashResult.charAt(i / 3 * 2);
cs[i + 1] = salt.charAt(i / 3);//輸出帶鹽,存儲鹽到hash值中;每兩個hash字符中間插入一個鹽字符
cs[i + 2] = hashResult.charAt(i / 3 * 2 + 1);
}
hashResult = new String(cs);
return hashResult;
} catch (Exception e) {
e.printStackTrace();
return e.toString();
}
}
        將salt存到hash值的操作也很簡單,假定輸出hash值是32字節,我們生成的是16字節的鹽,我們可以簡單的每兩個hash字符中間插入一個鹽字符。帶鹽也很簡單吧。
三、后台密碼存儲和驗證過程

        這里假定從前端傳到后台的密碼時明文。

1.注冊時存儲密碼

(1)用戶注冊時輸入的賬號、密碼p1從前端傳到后台;

(2)后台隨機生成一個salt;

(3)H(p1+salt)生成哈希值,將此哈希值帶鹽(存儲salt)后的結果hash1存儲到數據庫中;

2.登錄時驗證密碼

(1)用戶登陸時輸入的賬號、密碼p2從前端傳到后台;
(2)用登陸賬號在數據庫中查詢賬號相同的記錄,取其哈希值hash2

(3)從hash2獲取salt(保證存儲時和驗證時salt相同)

(4)H(p2+salt)生成哈希值hash3,判斷if(hash2==hash3);若相等登陸成功,否則登陸失敗。

四、java實現

哈希函數選擇MD5,javaApi中MD5沒有加鹽的過程,需要我們自己實現加鹽。關於Becrypt的加鹽更簡單,可以看我Becrypt那篇博客的源碼。

package EncryptAndDecrypt;

import java.security.MessageDigest;
import java.util.Random;

/**
* 散列加密之32位哈希值的MD5算法,調用JDK里的API
*ps:准確來說散列加密不是加密算法,因為它是不可逆的(只能加密,不能解密)
*/
public class MyMD5 {

private static char[] hex = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

public static void main(String[] args) throws Exception {
String input = "123456";
System.out.println("MD5加密" + "\n"
+ "明文:" + input + "\n"
+ "無鹽密文:" + MD5WithoutSalt(input));
System.out.println("帶鹽密文:" + MD5WithSalt(input,0));
}

/**
*@Author: DavidHuang
*@Time: 2018/5/11 14:55
*@return:
*@params: [inputStr] 輸入明文
*@Descrption: 不加鹽MD5
*/
public static String MD5WithoutSalt(String inputStr) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");//申明使用MD5算法,更改參數為"SHA"就是SHA算法了
return byte2HexStr(md.digest(inputStr.getBytes()));//哈希計算,轉換輸出
} catch (Exception e) {
e.printStackTrace();
return e.toString();
}

}

/**
*@Author: DavidHuang
*@Time: 2018/5/11 14:47
*@return:
*@params: [inputStr, type] inputStr是輸入的明文;type是處理類型,0表示注冊存hash值到庫時,1表示登錄驗證時
*@Descrption: MD5加鹽,鹽的獲取分兩種情況;輸入明文加鹽;輸出密文帶鹽(將salt存儲到hash值中)
*/
public static String MD5WithSalt(String inputStr, int type) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");//申明使用MD5算法,更改參數為"SHA"就是SHA算法了

String salt = null;
if (type == 0) {//注冊存hash值到庫時,new salt
salt = salt();
} else if (type == 1) {//登錄驗證時,使用從庫中查找到的hash值提取出的salt
String queriedHash=null;//從庫中查找到的hash值
salt=getSaltFromHash(queriedHash);
}

String inputWithSalt = inputStr + salt;//加鹽,輸入加鹽
String hashResult = byte2HexStr(md.digest(inputWithSalt.getBytes()));//哈希計算,轉換輸出
System.out.println("加鹽密文:"+hashResult);

/*將salt存儲到hash值中,用於登陸驗證密碼時使用相同的鹽*/
char[] cs = new char[48];
for (int i = 0; i < 48; i += 3) {
cs[i] = hashResult.charAt(i / 3 * 2);
cs[i + 1] = salt.charAt(i / 3);//輸出帶鹽,存儲鹽到hash值中;每兩個hash字符中間插入一個鹽字符
cs[i + 2] = hashResult.charAt(i / 3 * 2 + 1);
}
hashResult = new String(cs);
return hashResult;
} catch (Exception e) {
e.printStackTrace();
return e.toString();
}
}


/**
* @Author: DavidHuang
* @Time: 2018/5/10 21:14
* @return: salt
* @params:
* @Descrption: 自定義簡單生成鹽,是一個隨機生成的長度為16的字符串,每一個字符是隨機的十六進制字符
*/
public static String salt() {
Random random = new Random();
StringBuilder sb = new StringBuilder(16);
for (int i = 0; i < sb.capacity(); i++) {
sb.append(hex[random.nextInt(16)]);
}
return sb.toString();
}

/**
* @Author: DavidHuang
* @Time: 2018/5/11 14:08
* @return: 十六進制字符串
* @params: [bytes]
* @Descrption: 將字節數組轉換成十六進制字符串
*/
private static String byte2HexStr(byte[] bytes) {
/**
*@Author: DavidHuang
*@Time: 19:41 2018/5/10
*@return: java.lang.String
*@params: * @param bytes
*@Descrption:
*/
int len = bytes.length;
StringBuffer result = new StringBuffer();
for (int i = 0; i < len; i++) {
byte byte0 = bytes[i];
result.append(hex[byte0 >>> 4 & 0xf]);
result.append(hex[byte0 & 0xf]);
}
return result.toString();
}


/**
*@Author: DavidHuang
*@Time: 2018/5/11 14:32
*@return: 提取的salt
*@params: [hash] 3i byte帶鹽的hash值,帶鹽方法與MD5WithSalt中相同
*@Descrption: 從庫中查找到的hash值提取出的salt
*/
public static String getSaltFromHash(String hash){
StringBuilder sb=new StringBuilder();
char [] h=hash.toCharArray();
for(int i=0;i<hash.length();i+=3){
sb.append(h[i+1]);
}
return sb.toString();
}

}
第一次運行結果:

MD5加密
明文:123456
無鹽密文:E10ADC3949BA59ABBE56E057F20F883E
加鹽密文:80D05C08F8879B2C84BA0C40143D224F
帶鹽密文:8D0D8053C0F8F6887791B2BC804B7A0EC4901543DD2B241F
第二運行結果:
MD5加密
明文:123456
無鹽密文:E10ADC3949BA59ABBE56E057F20F883E
加鹽密文:2CFFE57B054378D926A6FF14A1985F22
帶鹽密文:21CF7FE957CB0354C37B8DC9256AD6F0F174A719785AF222
        可以看到對於相同明文,多次MD5哈希的無鹽密文相同,帶鹽密文和加鹽密文不同。由哈希函數的特征很容易明白無鹽密文相同。由於每次哈希計算生成的salt是隨機的,相當於每次哈希函數不同,所以帶鹽密文和加鹽密文不同。

 

 

由此可以看出加鹽后安全性更高了吧。

 

參考:https://blog.csdn.net/dingsai88/article/details/51637977

          https://blog.csdn.net/hao_hl1314/article/details/53141005
---------------------
作者:逍遙劍臣
來源:CSDN
原文:https://blog.csdn.net/DavidHuang2017/article/details/80283469
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM