消息摘要算法總結與實踐

序言

消息摘要算法平常使用的頻率很高,經常我們用它來驗證數據是否被篡改。 還有驗證網絡傳輸文件時,文件是否被篡改等等。

消息摘要算法有哪些

消息摘要算法主要分爲三類: 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的服務器文檔:
某SKD的服務器文檔

我們可以看到這個是把secret+uid+psid+roleid+amount+type+htnonce 這些參數值進行摘要,生成的值和原來的各個參數來作爲HTTP參數。 這個文檔的參數是按照這個規則來進行摘要的,有的會用參數的key進行排序然後來進行摘要。 具體的規則還是客戶端和服務器端進行商定。

  • 在Android領域,我們會比較apk中md5(指的是對Android簽名文件的摘要。 對apk解壓後的original\META-INF\XXX.RSA文件中),來比較這個apk是否被二次打包過。 其他的文件也是一樣,來判斷是否被修改過
    獲取apk中的信息

這個工具源碼: https://github.com/songshuilin/parseApk

總結

上面列出三種摘要算法系列: MD系列 、 SHA系列 、 MAC系列。 但是我們還會見到CRC系列,在WinRAR 、WinZIP等軟件中。
一般情況下安全性 MAC > SHA > MD > CRC

消息摘要特點:

  • 變長輸入、定長輸出 : 即 不管輸入的消息多長,計算出來的摘要的長度是固定的。
  • 輸入不同,摘要不同。 輸入相同,摘要相同 ;
  • 單向不可逆: 即 只能進行正向的摘要,而無法從摘要中恢復原來的任何信息。
  • “碰撞” 難找到: 好的摘要算法,很難找到碰撞,雖然碰撞是肯定存在。 即無法找到兩個不同的消息,但是他們的摘要相同。

一個遊戲技術人,分享Java、Python、H5、Android 等知識。同時會分享遊戲行業日常。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章