基於MessageDigest類編寫MD5通用工具類

suhuanzhen發表於2018-02-11

(一)基本介紹

Java的MessageDigest 類 可以提供MD5演算法或SHA演算法用於計算出資訊的摘要;它接收任意大小的資訊,並輸出計算後的固定長度的雜湊值。這個輸出的雜湊值就是我們所說的資訊摘要。

MD5演算法得到的摘要是固定長度為 128 bit 的二進位制串(一堆0和1,一共128個),為了更友好的表示摘要,一般都將 128位的二進位制串 轉為 32個16進位制位16個16進位制位 如下:

欄目1 欄目2
原始資訊 1234567890
16位小寫摘要 f82d132f9bb018ca
16位大寫摘要 F82D132F9BB018CA
32位小寫摘要 e807f1fcf82d132f9bb018ca6738a19f
32位大寫摘要 E807F1FCF82D132F9BB018CA6738A19F

(二)16位的摘要?

MD5演算法得到的摘要固定長度為 128 bit 也就是32個16進位制位(32位摘要),那麼怎麼得到16位的摘要呢? 其實從上面的表格中可以看出,16位的摘要是32位摘要值的中間部分,即32位摘要中第8~24位的部分。

(三)MessageDigest類計算MD5摘要的步驟

1,例項化MessageDigest物件

public static MesageDigest getInstance(String algorithm);
複製程式碼

演算法名不區分大小寫,所以下面的寫法都是正確的:

MesageDigest getInstance("MD5");
MesageDigest getInstance("md5");
MesageDigest getInstance("mD5");
複製程式碼

2,向MessageDigest傳送資訊

該步驟就是呼叫下面的某個方法來完成資訊的傳遞。

public void update(byte input);
public void update(byte[] input);
public void update(byte[] input, int offset, int len);
複製程式碼

3,計算資訊的摘要

最後呼叫下面的某個方法來計算摘要。

public byte[] digest();
public byte[] digest(byte[] input);
public int digest(byte[] buf,int offset,int len);
複製程式碼

4,將摘要轉為16進位制位的字串

為了更友好的表示摘要,一般都將 128位的二進位制串 轉為 32個16進位制位16個16進位制位 ,並以字串的形式表示。

摘要一般以字串的形式展示,所以在WEB應用中,用於表示密碼的MD5摘要的資料庫欄位一般設定為String password

雖然欄位名的字面意思表示賬戶密碼,但實際上只是賬戶密碼的MD5摘要,參考 摘要與加密的區別(以MD5演算法為例)

(四)基於MessageDigest類的MD5通用工具類實現

實現MD5通用工具類的方法有很多,下面我用清晰明瞭的程式碼去實現MD5Util通用工具類。

1,MD5工具類完整程式碼

import java.security.MessageDigest;

public class MD5Util {

    private static final String hexDigits[] = {"0", "1", "2", "3", "4", "5",
            "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};

    /**
     * 將1個位元組(1 byte = 8 bit)轉為 2個十六進位制位
     * 1個16進位制位 = 4個二進位制位 (即4 bit)
     * 轉換思路:最簡單的辦法就是先將byte轉為10進位制的int型別,然後將十進位制數轉十六進位制
     */
    private static String byteToHexString(byte b) {
        // byte型別賦值給int變數時,java會自動將byte型別轉int型別,從低位型別到高位型別自動轉換
        int n = b;

        // 將十進位制數轉十六進位制
        if (n < 0)
            n += 256;
        int d1 = n / 16;
        int d2 = n % 16;

        // d1和d2通過訪問陣列變數的方式轉成16進位制字串;比如 d1 為12 ,那麼就轉為"c"; 因為int型別不會有a,b,c,d,e,f等表示16進位制的字元
        return hexDigits[d1] + hexDigits[d2];
    }

    /**
     * 將位元組陣列裡每個位元組轉成2個16進位制位的字串後拼接起來
     */
    private static String byteArrayToHexString(byte b[]) {
        StringBuffer resultSb = new StringBuffer();
        for (int i = 0; i < b.length; i++){
            resultSb.append(byteToHexString(b[i]));
        }
        return resultSb.toString();
    }

    /**
     * MD5演算法,統一返回大寫形式的摘要結果,預設固定長度是 128bit 即 32個16進位制位
     * String origin :需要進行MD5計算的字串
     * String charsetname :MD5演算法的編碼
     */
    private static String MD5_32(String origin, String charsetname) {
        String resultString = null;
        try {
            // 1,建立MessageDigest物件
            MessageDigest md = MessageDigest.getInstance("MD5");
            // 2,向MessageDigest傳送資訊;傳入的資訊需要轉化為指定編碼的位元組陣列
            md.update(origin.getBytes( charsetname ));
            // 3,計算摘要
            byte[] bytesResult = md.digest();

            // 第2步和第3步可以合併成下面一步
            // byte[] bytesResult = md.digest(origin.getBytes(charsetname));

            // 4,將位元組陣列轉換為16進位制位
            resultString = byteArrayToHexString( bytesResult );
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 統一返回大寫形式的字串摘要
        return resultString.toUpperCase();

    }

    /**
     * 獲取 16位的MD5摘要,就是擷取32位結果的中間部分
     */
    private static String MD5_16(String origin, String charsetname) {
        return MD5_32(origin, charsetname).substring(8,24);
    }

    public static void main(String[] args){
        String origin = "1234567890";

        // 預設MD5計算得到128 bit的摘要,即32個 16進位制位
        String result_32 = MD5_32(origin, "utf-8");
        System.out.println(result_32);  // E807F1FCF82D132F9BB018CA6738A19F

        // 獲取16位的摘要
        String result_16 = MD5_16(origin, "utf-8");
        System.out.println(result_16);  // F82D132F9BB018CA
    }
}
複製程式碼

2,沒有呼叫update方法?

在很多時候,我們經常看到類似下面的寫法:

byte[] bytesResult = md.digest(origin.getBytes(charsetname));
複製程式碼

在這種寫法中並沒有程式碼顯式地呼叫update方法,但跟下面的寫法是一樣的,實際上也呼叫了update方法:

// 2,向MessageDigest傳送資訊;傳入的資訊需要轉化為指定編碼的位元組陣列
md.update(origin.getBytes( charsetname ));
// 3,計算摘要
byte[] bytesResult = md.digest();
複製程式碼

檢視該digest(byte[] input)方法的原始碼,可以看到該方法其實就是將第2步和第3步封裝成一個函式而已。

public byte[] digest(byte[] input) {
    update(input);
    return digest();
}
複製程式碼

相關文章