微信搜尋:碼農StayUp
主頁地址:https://gozhuyinglong.github.io
原始碼分享:https://github.com/gozhuyinglong/blog-demos
1. 定義
單向雜湊函式(one-way hash function)是指對不同的輸入值,通過單向雜湊函式進行計算,得到固定長度的輸出值。這個輸入值稱為訊息(message),輸出值稱為雜湊值(hash value)。
單向雜湊函式也被稱為訊息摘要函式(message digest function)、雜湊函式或者雜湊函式。輸入的訊息也稱為原像(pre-image)。輸出的雜湊值也稱為訊息摘要(message digest)或者指紋(fingerprint),相當於該訊息的身份證。
單向雜湊函式有多種實現演算法,常見的有:MD5、SHA-1、SHA-2和 SHA-3。
2. 特性
通過上面的定義,我們對單向雜湊函式的瞭解還是模糊的。下面介紹單向雜湊函式的特性,加深一下印象。
2.1 雜湊值長度固定
無論訊息的長度有多少,使用同一演算法計算出的雜湊值長度總是固定的。比如 MD5 演算法,無論輸入多少,產生的雜湊值長度總是 128 位元(16位元組)。
然而位元是計算機能夠識別的單位,而我們人類更習慣於使用十六進位制字串來表示(一個位元組佔用兩位十六進位制字元)。
2.2 訊息不同其雜湊值也不同
使用相同的訊息,產生的雜湊值一定相同。
使用不同的訊息,產生的雜湊值也不相同。哪怕只有一個位元的差別,得到的雜湊值也會有很大區別。
這一特性也叫做抗碰撞性,對於抗碰撞性弱的演算法,我們不應該使用。
2.3 具備單向性
只能通過訊息計算出雜湊值,無法通過雜湊值反算出訊息。
2.4 計算速度快
計算雜湊值的速度快。儘管訊息越長,計算雜湊值的時間也越長,但也會在短時間內完成。
3. 常見演算法
MD5 與 SHA-1 演算法已被攻破,不應該被用於新的用途;SHA-2 與 SHA-3 還是安全的,可以使用。
SHA-2包括:SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256。
SHA-3包括:SHA3-224、SHA3-256、SHA3-384、SHA3-512。
演算法名稱 | 雜湊值長度 | 是否安全 |
---|---|---|
MD5 | 128 | 不安全 |
SHA-1 | 160 | 不安全 |
SHA-224 | 224 | 安全 |
SHA-256 | 256 | 安全 |
SHA-384 | 384 | 安全 |
SHA-512 | 512 | 安全 |
SHA-512/224 | 224 | 安全 |
SHA-512/256 | 256 | 安全 |
SHA3-224 | 224 | 安全 |
SHA3-256 | 256 | 安全 |
SHA3-384 | 384 | 安全 |
SHA3-512 | 512 | 安全 |
4. 應用場景
單向雜湊函式並不能確保資訊的機密性,它是一種保證資訊完整性的密碼技術。下面來看它的應用場景。
4.1 使用者密碼保護
使用者在設定密碼時,不記錄密碼本身,只記錄密碼的雜湊值,只有使用者自己知道密碼的明文。校驗密碼時,只要輸入的密碼正確,得到的雜湊值一定是一樣的,表示校驗正確。
為了防止彩虹表破解,還可以為密碼進行加鹽處理,只要驗證密碼時,使用相同的鹽即可完成校驗。
使用雜湊值儲存密碼的好處是:即使資料庫被盜,也無法將密文反推出明文是什麼,使密碼儲存更安全。
4.2 介面驗籤
為了保證介面的安全,可以採用簽名的方式傳送。
傳送者與接收者要有一個共享祕鑰。當傳送者向接收者傳送請求時,引數中附加上簽名(簽名由共享祕鑰 + 業務引數,進行單向雜湊函式加密生成)。接收者收到後,使用相同的方式生成簽名,再與收到的簽名進行比對,如果一致,驗籤成功。
這樣即可以驗證業務引數是否被篡改,又能驗明傳送者的身份。
4.3 檔案完整性校驗
檔案被掛載到網站時,同時也附上其雜湊值和演算法,比如 Tomcat 官網。
使用者下載後,計算其雜湊值,對比結果是否相同,從而校驗檔案的完整性。
4.4 雲盤秒傳
當我們將自己喜歡的視訊放到網盤上時,發現只用了幾秒的時間就上傳成功了,而這個檔案有幾個G大小,是怎麼做到的呢?
其實這個“秒傳”功能可以利用單向雜湊函式來實現。
當我們上傳一個檔案時,雲盤客戶端會先為該檔案生成一個雜湊值。拿著這個雜湊值去資料庫中匹配,如果匹配到,說明該檔案已經在雲伺服器存在。只需將該雜湊值與使用者進行關聯,便可完成本次“上傳”。
這樣,一個檔案在雲伺服器上只會存一份,大大節約了雲伺服器的空間。
5. 程式碼實現
JDK的 java.security.MessageDigest
類為我們提供了訊息摘要演算法,用於 MD5和SHA的雜湊值生成。下面程式碼做了簡單的封裝,便於直接使用。
public class MDUtil {
/**
* MD5 加密
*
* @param data 要加密的資料
* @return 32位十六進位制字串
*/
public static String MD5(byte[] data) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] bytes = md.digest(data);
return bytesToHexString(bytes);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
/**
* MD5 加密
*
* @param data 要加密的資料
* @return 32位十六進位制字串
*/
public static String MD5(String data) {
return MD5(data.getBytes());
}
/**
* SHA-1 加密
*
* @param data 要加密的資料
* @return 40位十六進位制字串
*/
public static String SHA1(byte[] data) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] bytes = md.digest(data);
return bytesToHexString(bytes);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
/**
* SHA-1 加密
*
* @param data 要加密的資料
* @return 40位十六進位制字串
*/
public static String SHA1(String data) {
return SHA1(data.getBytes());
}
/**
* SHA-224 加密
*
* @param data 要加密的資料
* @return 56位十六進位制字串
*/
public static String SHA224(byte[] data) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-224");
byte[] bytes = md.digest(data);
return bytesToHexString(bytes);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
/**
* SHA-224 加密
*
* @param data 要加密的資料
* @return 56位十六進位制字串
*/
public static String SHA224(String data) {
return SHA224(data.getBytes());
}
/**
* SHA-256 加密
*
* @param data 要加密的資料
* @return 64位十六進位制字串
*/
public static String SHA256(byte[] data) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] bytes = md.digest(data);
return bytesToHexString(bytes);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
/**
* SHA-256 加密
*
* @param data 要加密的資料
* @return 64位十六進位制字串
*/
public static String SHA256(String data) {
return SHA256(data.getBytes());
}
/**
* SHA-384 加密
*
* @param data 要加密的資料
* @return 96位十六進位制字串
*/
public static String SHA384(byte[] data) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-384");
byte[] bytes = md.digest(data);
return bytesToHexString(bytes);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
/**
* SHA-384 加密
*
* @param data 要加密的資料
* @return 96位十六進位制字串
*/
public static String SHA384(String data) {
return SHA384(data.getBytes());
}
/**
* SHA-512 加密
*
* @param data 要加密的資料
* @return 128位十六進位制字串
*/
public static String SHA512(byte[] data) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-512");
byte[] bytes = md.digest(data);
return bytesToHexString(bytes);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
/**
* SHA-512 加密
*
* @param data 要加密的資料
* @return 128位十六進位制字串
*/
public static String SHA512(String data) {
return SHA512(data.getBytes());
}
/**
* 將位元組陣列轉換為十六進位制字串
*
* @param bytes 位元組陣列
* @return 十六進位制字串
*/
private static String bytesToHexString(byte[] bytes) {
StringBuilder hexValue = new StringBuilder();
for (byte b : bytes) {
int val = b & 0xFF;
if (val < 16) {
hexValue.append("0");
}
hexValue.append(Integer.toHexString(val));
}
return hexValue.toString();
}
}
下面分別使用這些演算法計算“123456”的雜湊值:
public static void main(String[] args) {
System.out.println("MD5\t\t" + MDUtil.MD5("123456"));
System.out.println("SHA-1\t" + MDUtil.SHA1("123456"));
System.out.println("SHA-224\t" + MDUtil.SHA224("123456"));
System.out.println("SHA-256\t" + MDUtil.SHA256("123456"));
System.out.println("SHA-384\t" + MDUtil.SHA384("123456"));
System.out.println("SHA-512\t" + MDUtil.SHA512("123456"));
}
輸出結果:
MD5 e10adc3949ba59abbe56e057f20f883e
SHA-1 7c4a8d09ca3762af61e59520943dc26494f8941b
SHA-224 f8cdb04495ded47615258f9dc6a3f4707fd2405434fefc3cbf4ef4e6
SHA-256 8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92
SHA-384 0a989ebc4a77b56a6e2bb7b19d995d185ce44090c13e2984b7ecc6d446d4b61ea9991b76a4c2f04b1b4d244841449454
SHA-512 ba3253876aed6bc22d4a6ff53d8406c6ad864195ed144ab5c87621b6c233b548baeae6956df346ec8c17f5ea10f35ee3cbc514797ed7ddd3145464e2a0bab413
我用的是Java8,還不支援 SHA-3,所以上面程式碼只封裝了MD5、SHA-1和SHA-2。
從Java9開始支援SHA-3
6. 完整程式碼
完整程式碼請訪問我的Github,若對你有幫助,歡迎給個Star,謝謝!