一文搞懂單向散列函數(shù)

微信搜索:碼農(nóng)StayUp
主頁地址:https://gozhuyinglong.github.io
源碼分享:https://github.com/gozhuyinglong/blog-demos

1. 定義

單向散列函數(shù)(one-way hash function)是指對不同的輸入值,通過單向散列函數(shù)進(jìn)行計算,得到固定長度的輸出值。這個輸入值稱為消息(message),輸出值稱為散列值(hash value)。

單向散列函數(shù)

單向散列函數(shù)也被稱為消息摘要函數(shù)(message digest function)、哈希函數(shù)或者雜湊函數(shù)。輸入的消息也稱為原像(pre-image)。輸出的散列值也稱為消息摘要(message digest)或者指紋(fingerprint),相當(dāng)于該消息的身份證。

單向散列函數(shù)有多種實現(xiàn)算法,常見的有:MD5、SHA-1、SHA-2SHA-3。

2. 特性

通過上面的定義,我們對單向散列函數(shù)的了解還是模糊的。下面介紹單向散列函數(shù)的特性,加深一下印象。

2.1 散列值長度固定

無論消息的長度有多少,使用同一算法計算出的散列值長度總是固定的。比如 MD5 算法,無論輸入多少,產(chǎn)生的散列值長度總是 128 比特(16字節(jié))。

然而比特是計算機能夠識別的單位,而我們?nèi)祟惛?xí)慣于使用十六進(jìn)制字符串來表示(一個字節(jié)占用兩位十六進(jìn)制字符)。

散列值長度固定

2.2 消息不同其散列值也不同

使用相同的消息,產(chǎn)生的散列值一定相同。

使用不同的消息,產(chǎn)生的散列值也不相同。哪怕只有一個比特的差別,得到的散列值也會有很大區(qū)別。

這一特性也叫做抗碰撞性,對于抗碰撞性弱的算法,我們不應(yīng)該使用。

消息不同其散列值也不同

2.3 具備單向性

只能通過消息計算出散列值,無法通過散列值反算出消息。

具備單向性

2.4 計算速度快

計算散列值的速度快。盡管消息越長,計算散列值的時間也越長,但也會在短時間內(nèi)完成。

3. 常見算法

MD5 與 SHA-1 算法已被攻破,不應(yīng)該被用于新的用途;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. 應(yīng)用場景

單向散列函數(shù)并不能確保信息的機密性,它是一種保證信息完整性的密碼技術(shù)。下面來看它的應(yīng)用場景。

4.1 用戶密碼保護(hù)

用戶在設(shè)置密碼時,不記錄密碼本身,只記錄密碼的散列值,只有用戶自己知道密碼的明文。校驗密碼時,只要輸入的密碼正確,得到的散列值一定是一樣的,表示校驗正確。

為了防止彩虹表破解,還可以為密碼進(jìn)行加鹽處理,只要驗證密碼時,使用相同的鹽即可完成校驗。

用戶密碼保護(hù)

使用散列值存儲密碼的好處是:即使數(shù)據(jù)庫被盜,也無法將密文反推出明文是什么,使密碼保存更安全。

4.2 接口驗簽

為了保證接口的安全,可以采用簽名的方式發(fā)送。

發(fā)送者與接收者要有一個共享秘鑰。當(dāng)發(fā)送者向接收者發(fā)送請求時,參數(shù)中附加上簽名(簽名由共享秘鑰 + 業(yè)務(wù)參數(shù),進(jìn)行單向散列函數(shù)加密生成)。接收者收到后,使用相同的方式生成簽名,再與收到的簽名進(jìn)行比對,如果一致,驗簽成功。

這樣即可以驗證業(yè)務(wù)參數(shù)是否被篡改,又能驗明發(fā)送者的身份。

接口驗簽

4.3 文件完整性校驗

文件被掛載到網(wǎng)站時,同時也附上其散列值和算法,比如 Tomcat 官網(wǎng)。

Tomcat官網(wǎng)下載頁面

用戶下載后,計算其散列值,對比結(jié)果是否相同,從而校驗文件的完整性。

4.4 云盤秒傳

當(dāng)我們將自己喜歡的視頻放到網(wǎng)盤上時,發(fā)現(xiàn)只用了幾秒的時間就上傳成功了,而這個文件有幾個G大小,是怎么做到的呢?

其實這個“秒傳”功能可以利用單向散列函數(shù)來實現(xiàn)。

當(dāng)我們上傳一個文件時,云盤客戶端會先為該文件生成一個散列值。拿著這個散列值去數(shù)據(jù)庫中匹配,如果匹配到,說明該文件已經(jīng)在云服務(wù)器存在。只需將該散列值與用戶進(jìn)行關(guān)聯(lián),便可完成本次“上傳”。

這樣,一個文件在云服務(wù)器上只會存一份,大大節(jié)約了云服務(wù)器的空間。

云盤秒傳

5. 代碼實現(xiàn)

JDK的 java.security.MessageDigest 類為我們提供了消息摘要算法,用于 MD5和SHA的散列值生成。下面代碼做了簡單的封裝,便于直接使用。

public class MDUtil {

    /**
     * MD5 加密
     *
     * @param data 要加密的數(shù)據(jù)
     * @return 32位十六進(jìn)制字符串
     */
    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 要加密的數(shù)據(jù)
     * @return 32位十六進(jìn)制字符串
     */
    public static String MD5(String data) {
        return MD5(data.getBytes());
    }

    /**
     * SHA-1 加密
     *
     * @param data 要加密的數(shù)據(jù)
     * @return 40位十六進(jìn)制字符串
     */
    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 要加密的數(shù)據(jù)
     * @return 40位十六進(jìn)制字符串
     */
    public static String SHA1(String data) {
        return SHA1(data.getBytes());
    }

    /**
     * SHA-224 加密
     *
     * @param data 要加密的數(shù)據(jù)
     * @return 56位十六進(jìn)制字符串
     */
    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 要加密的數(shù)據(jù)
     * @return 56位十六進(jìn)制字符串
     */
    public static String SHA224(String data) {
        return SHA224(data.getBytes());
    }

    /**
     * SHA-256 加密
     *
     * @param data 要加密的數(shù)據(jù)
     * @return 64位十六進(jìn)制字符串
     */
    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 要加密的數(shù)據(jù)
     * @return 64位十六進(jìn)制字符串
     */
    public static String SHA256(String data) {
        return SHA256(data.getBytes());
    }

    /**
     * SHA-384 加密
     *
     * @param data 要加密的數(shù)據(jù)
     * @return 96位十六進(jìn)制字符串
     */
    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 要加密的數(shù)據(jù)
     * @return 96位十六進(jìn)制字符串
     */
    public static String SHA384(String data) {
        return SHA384(data.getBytes());
    }

    /**
     * SHA-512 加密
     *
     * @param data 要加密的數(shù)據(jù)
     * @return 128位十六進(jìn)制字符串
     */
    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 要加密的數(shù)據(jù)
     * @return 128位十六進(jìn)制字符串
     */
    public static String SHA512(String data) {
        return SHA512(data.getBytes());
    }

    /**
     * 將字節(jié)數(shù)組轉(zhuǎn)換為十六進(jìn)制字符串
     *
     * @param bytes 字節(jié)數(shù)組
     * @return 十六進(jìn)制字符串
     */
    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"));
}

輸出結(jié)果:

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。

Java8支持的算法

從Java9開始支持SHA-3

Java9支持的算法

6. 完整代碼

完整代碼請訪問我的Github,若對你有幫助,歡迎給個Star,謝謝!

https://github.com/gozhuyinglong/blog-demos/blob/main/java-source-analysis/src/main/java/io/github/gozhuyinglong/utils/MDUtil.java

7. 推薦閱讀

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容