序言
消息摘要算法平常使用的頻率很高,經常我們用它來驗證數據是否被篡改。 還有驗證網絡傳輸文件時,文件是否被篡改等等。
消息摘要算法有哪些
消息摘要算法主要分爲三類: MD 、 SHA 、 MAC
- MD(Message Digest) : 消息摘要
- SHA(Secure Hash Algorithm) : 安全散列
- MAC(Message Authentication Code) : 消息認證碼
主要用途 : 驗證消息的完整性
主要特性:
- 輸入的消息不受限制,但是輸出的消息(經過消息摘要算法後的消息)固定;
- 如果輸入的消息不同,輸出的消息也肯定不同;
- 消息摘要算法是不可逆的; 即不能從摘要後的消息解析出任何源信息相關的信息
MD(Message Digest) : 消息摘要
MD系列算法一般有: MD2 、 MD4 、 MD5, 數字的值越大代表算法的版本越高,安全就越強。 即 MD5 > MD4 > MD2 。 自然破解難度MD5最高,平常用的最多的也是MD5算法。
MD系列算法輸出信息的摘要長度都是固定的128bit ,即16字節;
因爲JDK不支持MD4算法, 我們需引用第三方的依賴。在 pom.xml下加入這段才能使用MD4
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.62</version>
</dependency>
代碼示例 :
/**
* @program
* @Desc MD消息摘要算法 測試
* @Author 遊戲人日常
* @CreateTime 2019/07/09--15:14
*/
public class MDTest {
private static final String src = "helloWorld";
public static void main(String[] args) {
jdkMD5();
jdkMD2();
bcMD4();
jdkAndBcMd4();
}
//JDK 實現
public static void jdkMD5() {
try {
//得到消息摘要對象
MessageDigest md = MessageDigest.getInstance("MD5");
//對src 字符串進行消息摘要
byte[] md5Bytes = md.digest(src.getBytes());
//對得到的字節數組 轉爲16進制
String result = new BigInteger(md5Bytes).toString(16);
System.out.println(result);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
//JDK 實現
public static void jdkMD2() {
try {
//得到消息摘要對象
MessageDigest md = MessageDigest.getInstance("MD2");
//對src 字符串進行消息摘要
byte[] md5Bytes = md.digest(src.getBytes());
//對得到的字節數組 轉爲16進制
String result = new BigInteger(md5Bytes).toString(16);
System.out.println(result);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
//結合JDK 來實現
public static void jdkAndBcMd4() {
try {
//加入BouncyCastle的支持 也可以不通過代碼,需在Java/jdk1.8.0_181/jre/lib/security/java.security加入
Security.addProvider(new BouncyCastleProvider());
// 得到MD 消息摘要對象
MessageDigest md = MessageDigest.getInstance("MD4");
//對src 字符串進行消息摘要
byte[] mD4Bytes = md.digest(src.getBytes());
//對得到的字節數組 轉爲16進制
String result = new BigInteger(mD4Bytes).toString(16);
System.out.println(result);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
//通過 第三方 bouncy castle 來 實現
public static void bcMD4() {
//創建 MD4Digest 對象
Digest md4Digest = new MD4Digest();
//更新消息摘要 通過src字節數組
md4Digest.update(src.getBytes(), 0, src.getBytes().length);
//初始化字節數組大小
byte[] mD4Bytes = new byte[md4Digest.getDigestSize()];
//關閉摘要,然後得到摘要後的值
md4Digest.doFinal(mD4Bytes, 0);
//對得到的字節數組 轉爲16進制
String result = new BigInteger(mD4Bytes).toString(16);
System.out.println(result);
}
}
SHA(Secure Hash Algorithm) : 安全散列
SHA系列算法從版本上一般分爲兩類: SHA-1 和 SHA-2
SHA-1就是我們常見的SHA1 , SHA-2主要有SHA224 、 SHA256 、 SHA384 、 SHA512等等,不同的數字代表輸出消息的位數, 與MD系列算法一樣,數字高表示算法的安全性越高,但不同於MD系列算法的是,SHA系列算法輸出的信息摘要長度不一樣。
- SHA1 摘要算法後,輸出的信息爲160bit, 即20字節;
- SHA224 摘要算法後,輸出的信息爲224bit, 即28字節;
- SHA256 摘要算法後,輸出的信息爲256bit, 即32字節;
- SHA384 摘要算法後,輸出的信息爲384bit, 即48字節;
- SHA512 摘要算法後,輸出的信息爲512bit, 即64字節;
代碼示例:
**
* @program
* @Desc SHA 測試
* @Author 遊戲人日常
* @CreateTime 2019/07/09--17:44
*/
public class SHATest {
public static final String src="helloWorld";
public static void main(String [] args){
jdkSHA1();
bcSHA224();
jdkSHA256();
jdkSHA384();
jdkSHA512();
}
//JDK實現
public static void jdkSHA1(){
try {
//得到消息摘要對象
MessageDigest md=MessageDigest.getInstance("SHA");
//對src 字符串進行消息摘要
byte[] md5Bytes = md.digest(src.getBytes());
//對得到的字節數組 轉爲16進制
String result = new BigInteger(md5Bytes).toString(16);
System.out.println(result);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
// 通過 第三方 bouncy castle 來 實現
public static void bcSHA224(){
//創建 MD4Digest 對象
Digest digest = new SHA224Digest();
//更新消息摘要 通過src字節數組
digest.update(src.getBytes(), 0, src.getBytes().length);
//初始化字節數組大小
byte[] mD4Bytes = new byte[digest.getDigestSize()];
//關閉摘要,然後返回摘要後的值
digest.doFinal(mD4Bytes, 0);
//對得到的字節數組 轉爲16進制
String result = new BigInteger(mD4Bytes).toString(16);
System.out.println(result);
}
//JDK實現
public static void jdkSHA256(){
try {
//得到消息摘要對象
MessageDigest md=MessageDigest.getInstance("SHA-256");
//對src 字符串進行消息摘要
byte[] md5Bytes = md.digest(src.getBytes());
//對得到的字節數組 轉爲16進制
String result = new BigInteger(md5Bytes).toString(16);
System.out.println(result);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
//JDK實現
public static void jdkSHA384(){
try {
//得到消息摘要對象
MessageDigest md=MessageDigest.getInstance("SHA-384");
//對src 字符串進行消息摘要
byte[] md5Bytes = md.digest(src.getBytes());
//對得到的字節數組 轉爲16進制
String result = new BigInteger(md5Bytes).toString(16);
System.out.println(result);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
//JDK實現
public static void jdkSHA512(){
try {
//得到消息摘要對象
MessageDigest md=MessageDigest.getInstance("SHA-512");
//對src 字符串進行消息摘要
byte[] md5Bytes = md.digest(src.getBytes());
//對得到的字節數組 轉爲16進制
String result = new BigInteger(md5Bytes).toString(16);
System.out.println(result);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
}
MAC(Message Authentication Code) : 消息認證碼
MAC算法不同於MD和SHA,因爲在MD和SHA算法基礎上添加了密鑰。 所以安全性相比MD和SHA系列更高。
MD系列:HmacMD2 、HmacMD4 、HmacMD5
SHA系列: HmacSHA1 、 HmacSHA224 、HmacSHA256 、HmacSHA384 、HmacSHA512
摘要後的長度跟MD和SHA系列一樣。 如HmacMD5摘要長度跟MD5一樣, 即160bit,20字節;
代碼示例:
/**
* @program
* @Desc MAC 測試
* @Author 遊戲人日常
* @CreateTime 2019/07/09--19:44
*/
public class MACTest {
public static final String src="helloWorld";
public static void main(String [] args){
jdkHmacMD5();
HmacSHA1();
}
//JDK實現
public static void jdkHmacMD5(){
try {
//獲取key生成器對象
KeyGenerator keyGenerator=KeyGenerator.getInstance("HmacMD5");
//獲取密鑰對象
SecretKey secretKey=keyGenerator.generateKey();
//獲取密鑰
//byte [] key=secretKey.getEncoded();
//可以自己設置密鑰的來源, 必須是16進制, 長度是2的倍數
byte [] key=Hex.decode("123456789abcde");
//還原密鑰 第二個參數是算法名稱
SecretKey restoreSecretKey= new SecretKeySpec(key,"HmacMD5");
//獲取Mac對象
Mac mac=Mac.getInstance(restoreSecretKey.getAlgorithm());
//初始化Mac
mac.init(restoreSecretKey);
//對src執行摘要
byte[] hmacMD5Bytes= mac.doFinal(src.getBytes());
//對摘要的信息字節數組 轉爲16進制
String result = new BigInteger(hmacMD5Bytes).toString(16);
System.out.println(result);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
}
}
//JDK實現
public static void HmacSHA1(){
try {
//獲取key生成器對象
KeyGenerator keyGenerator=KeyGenerator.getInstance("HmacSHA1");
//獲取密鑰對象
SecretKey secretKey=keyGenerator.generateKey();
//獲取密鑰
//byte [] key=secretKey.getEncoded();
//可以自己設置密鑰的來源, 必須是16進制, 長度是2的倍數
byte [] key=Hex.decode("123456789abcde");
//還原密鑰 第二個參數是算法名稱
SecretKey restoreSecretKey= new SecretKeySpec(key,"HmacSHA1");
//獲取Mac對象
Mac mac=Mac.getInstance(restoreSecretKey.getAlgorithm());
//初始化Mac
mac.init(restoreSecretKey);
//對src執行摘要
byte[] hmacMD5Bytes= mac.doFinal(src.getBytes());
//對摘要的信息字節數組 轉爲16進制
String result = new BigInteger(hmacMD5Bytes).toString(16);
System.out.println(result);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
}
}
}
//其他的摘要實現一樣,大致都差不多,就不實現了。
密鑰不同,得到的摘要消息肯定不同。 密鑰的來源可以自己來構建,但必須滿足是16進制, 長度是2的倍數。
應用場景
- 登陸註冊時:用戶註冊時,我們平常會把關鍵的信息會進行摘要後,如:密碼。 然後持久化在數據庫中。 用戶登陸時:我們就對密碼進行摘要然後去數據庫去查詢。 一般採取的摘要算法是MD5。
註冊登陸時序圖如下:
上面只是個簡單的用戶註冊和登陸流程,爲什麼要對密碼進行摘要,不進行摘要的話,一旦如果數據庫中的數據泄露了,如: 用戶名和密碼, 別人就可以拿這個用戶名和密碼去登陸。 摘要的話,別人即使獲取到用戶名和密碼, 也是登陸不成功。 但是現在的MD5被國人破解了。
- 網絡傳輸數據時: 有些安全性比較高的,會對HTTP/HTTPS請求中的參數進行摘要,然後把摘要信息一併傳輸。 請求中參數具體怎麼摘要這個自己定。一般還會提供個key。
某個SDK的服務器文檔:
我們可以看到這個是把secret+uid+psid+roleid+amount+type+htnonce 這些參數值進行摘要,生成的值和原來的各個參數來作爲HTTP參數。 這個文檔的參數是按照這個規則來進行摘要的,有的會用參數的key進行排序然後來進行摘要。 具體的規則還是客戶端和服務器端進行商定。
- 在Android領域,我們會比較apk中md5(指的是對Android簽名文件的摘要。 對apk解壓後的original\META-INF\XXX.RSA文件中),來比較這個apk是否被二次打包過。 其他的文件也是一樣,來判斷是否被修改過
總結
上面列出三種摘要算法系列: MD系列 、 SHA系列 、 MAC系列。 但是我們還會見到CRC系列,在WinRAR 、WinZIP等軟件中。
一般情況下安全性 MAC > SHA > MD > CRC
消息摘要特點:
- 變長輸入、定長輸出 : 即 不管輸入的消息多長,計算出來的摘要的長度是固定的。
- 輸入不同,摘要不同。 輸入相同,摘要相同 ;
- 單向不可逆: 即 只能進行正向的摘要,而無法從摘要中恢復原來的任何信息。
- “碰撞” 難找到: 好的摘要算法,很難找到碰撞,雖然碰撞是肯定存在。 即無法找到兩個不同的消息,但是他們的摘要相同。