spring項目中使用MD5加密方式


百度百科中這樣解釋到MD5加密:

MD5信息摘要算法(英語:MD5 Message-Digest Algorithm),一種被廣泛使用的密碼散列函數,可以產生出一個128位(16字節)的散列值(hash value),用於確保信息傳輸完整一致。MD5由美國密碼學家羅納德·李維斯特(Ronald Linn Rivest)設計,於1992年公開,用以取代MD4算法。這套算法的程序在 RFC 1321 標准中被加以規范。1996年后該算法被證實存在弱點,可以被加以破解,對於需要高度安全性的數據,專家一般建議改用其他算法,如SHA-2。2004年,證實MD5算法無法防止碰撞(collision),因此不適用於安全性認證,如SSL公開密鑰認證或是數字簽名等用途

說起到MD5加密方式,我們先來了解下HASH算法:

哈希算法(Hash)又稱摘要算法(Digest),它的作用是:對任意一組輸入數據進行計算,得到一個固定長度的輸出摘要

哈希算法最重要的特點就是

  • 相同的輸入一定得到相同的輸出;
  • 不同的輸入大概率得到不同的輸出。

哈希算法的目的就是為了驗證原始數據是否被篡改

Java字符串的hashCode()就是一個哈希算法,它的輸入是任意字符串,輸出是固定的4字節int整數:

"hello".hashCode(); // 0x5e918d2
"hello, java".hashCode(); // 0x7a9d88e8
"hello, bob".hashCode(); // 0xa0dbae2f

兩個相同的字符串永遠會計算出相同的hashCode,否則基於hashCode定位的HashMap就無法正常工作。這也是為什么當我們自定義一個class時,覆寫equals()方法時我們必須正確覆寫hashCode()方法。

哈希碰撞:

哈希碰撞是指,兩個不同的輸入得到了相同的輸出:

"AaAaAa".hashCode(); // 0x7460e8c0
"BBAaBB".hashCode(); // 0x7460e8c0 

那么有人會問哈希碰撞是否可以避免?答案是否定的。碰撞是一定會出現的,因為輸出的字節長度是固定的,StringhashCode()輸出是4字節整數,最多只有4294967296種輸出,但輸入的數據長度是不固定的,有無數種輸入。所以,哈希算法是把一個無限的輸入集合映射到一個有限的輸出集合,必然會產生碰撞。

碰撞不可怕,我們擔心的不是碰撞,而是碰撞的概率,因為碰撞概率的高低關系到哈希算法的安全性。一個安全的哈希算法必須滿足:

  • 碰撞概率低;
  • 不能猜測輸出。

不能猜測輸出是指,輸入的任意一個bit的變化會造成輸出完全不同,這樣就很難從輸出反推輸入(只能依靠暴力窮舉)。假設一種哈希算法有如下規律:

hashA("java001") = "123456"
hashA("java002") = "123457"
hashA("java003") = "123458"

那么很容易從輸出123459反推輸入,這種哈希算法就不安全。安全的哈希算法從輸出是看不出任何規律的:

hashB("java001") = "123456"
hashB("java002") = "580271"
hashB("java003") = ???

常用的哈希算法有:

算法 輸出長度(位) 輸出長度(字節)
MD5 128 bits 16 bytes
SHA-1 160 bits 20 bytes
RipeMD-160 160 bits 20 bytes
SHA-256 256 bits 32 bytes
SHA-512 512 bits 64 bytes

  

  

 

 

 

 

 

根據碰撞概率,哈希算法的輸出長度越長,就越難產生碰撞,也就越安全。

Java標准庫提供了常用的哈希算法,並且有一套統一的接口。我們以MD5算法為例,看看如何對輸入計算哈希:

import java.math.BigInteger;
import java.security.MessageDigest;

public class Main {
    public static void main(String[] args) throws Exception {
        // 創建一個MessageDigest實例:
        MessageDigest md = MessageDigest.getInstance("MD5");
        // 反復調用update輸入數據:
        md.update("Hello".getBytes("UTF-8"));
        md.update("World".getBytes("UTF-8"));
        byte[] result = md.digest(); // 16 bytes: 68e109f0f40ca72a15e05cc22786f8e6
        System.out.println(new BigInteger(1, result).toString(16));
    }
}

MessageDigest時,我們首先根據哈希算法獲取一個MessageDigest實例,然后,反復調用update(byte[])輸入數據。當輸入結束后,調用digest()方法獲得byte[]數組表示的摘要,最后,把它轉換為十六進制的字符串。

運行上述代碼,可以得到輸入HelloWorld的MD5是68e109f0f40ca72a15e05cc22786f8e6

哈希算法的另一個重要用途是存儲用戶口令。如果直接將用戶的原始口令存放到數據庫中,會產生極大的安全風險:

  • 數據庫管理員能夠看到用戶明文口令;
  • 數據庫數據一旦泄漏,黑客即可獲取用戶明文口令。

不存儲用戶的原始口令,那么如何對用戶進行認證?

方法是存儲用戶口令的哈希,例如,MD5。

在用戶輸入原始口令后,系統計算用戶輸入的原始口令的MD5並與數據庫存儲的MD5對比,如果一致,說明口令正確,否則,口令錯誤。

因此,數據庫存儲用戶名和口令的表內容應該像下面這樣:

username password
bob f30aa7a662c728b7407c54ae6bfd27d1
alice 25d55ad283aa400af464c76d713c07ad
tim bed128365216c019988915ed3add75fb

 

 

 

 

 

這樣一來,數據庫管理員看不到用戶的原始口令。即使數據庫泄漏,黑客也無法拿到用戶的原始口令。想要拿到用戶的原始口令,必須用暴力窮舉的方法,一個口令一個口令地試,直到某個口令計算的MD5恰好等於指定值。使用哈希口令時,還要注意防止彩虹表攻擊。

在了解了Hash算法后我們再次回到MD5算法加密

在spring中是自帶了MD5加密的工具類的:

在項目中搜索DigestUtils

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.util;

import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public abstract class DigestUtils {
    private static final String MD5_ALGORITHM_NAME = "MD5";
    private static final char[] HEX_CHARS = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    public DigestUtils() {
    }

    public static byte[] md5Digest(byte[] bytes) {
        return digest("MD5", bytes);
    }

    public static byte[] md5Digest(InputStream inputStream) throws IOException {
        return digest("MD5", inputStream);
    }

    public static String md5DigestAsHex(byte[] bytes) {
        return digestAsHexString("MD5", bytes);
    }

    public static String md5DigestAsHex(InputStream inputStream) throws IOException {
        return digestAsHexString("MD5", inputStream);
    }

    public static StringBuilder appendMd5DigestAsHex(byte[] bytes, StringBuilder builder) {
        return appendDigestAsHex("MD5", bytes, builder);
    }

    public static StringBuilder appendMd5DigestAsHex(InputStream inputStream, StringBuilder builder) throws IOException {
        return appendDigestAsHex("MD5", inputStream, builder);
    }

    private static MessageDigest getDigest(String algorithm) {
        try {
            return MessageDigest.getInstance(algorithm);
        } catch (NoSuchAlgorithmException var2) {
            throw new IllegalStateException("Could not find MessageDigest with algorithm \"" + algorithm + "\"", var2);
        }
    }

    private static byte[] digest(String algorithm, byte[] bytes) {
        return getDigest(algorithm).digest(bytes);
    }

    private static byte[] digest(String algorithm, InputStream inputStream) throws IOException {
        MessageDigest messageDigest = getDigest(algorithm);
        if (inputStream instanceof UpdateMessageDigestInputStream) {
            ((UpdateMessageDigestInputStream)inputStream).updateMessageDigest(messageDigest);
            return messageDigest.digest();
        } else {
            byte[] buffer = new byte[4096];
            boolean var4 = true;

            int bytesRead;
            while((bytesRead = inputStream.read(buffer)) != -1) {
                messageDigest.update(buffer, 0, bytesRead);
            }

            return messageDigest.digest();
        }
    }

    private static String digestAsHexString(String algorithm, byte[] bytes) {
        char[] hexDigest = digestAsHexChars(algorithm, bytes);
        return new String(hexDigest);
    }

    private static String digestAsHexString(String algorithm, InputStream inputStream) throws IOException {
        char[] hexDigest = digestAsHexChars(algorithm, inputStream);
        return new String(hexDigest);
    }

    private static StringBuilder appendDigestAsHex(String algorithm, byte[] bytes, StringBuilder builder) {
        char[] hexDigest = digestAsHexChars(algorithm, bytes);
        return builder.append(hexDigest);
    }

    private static StringBuilder appendDigestAsHex(String algorithm, InputStream inputStream, StringBuilder builder) throws IOException {
        char[] hexDigest = digestAsHexChars(algorithm, inputStream);
        return builder.append(hexDigest);
    }

    private static char[] digestAsHexChars(String algorithm, byte[] bytes) {
        byte[] digest = digest(algorithm, bytes);
        return encodeHex(digest);
    }

    private static char[] digestAsHexChars(String algorithm, InputStream inputStream) throws IOException {
        byte[] digest = digest(algorithm, inputStream);
        return encodeHex(digest);
    }

    private static char[] encodeHex(byte[] bytes) {
        char[] chars = new char[32];

        for(int i = 0; i < chars.length; i += 2) {
            byte b = bytes[i / 2];
            chars[i] = HEX_CHARS[b >>> 4 & 15];
            chars[i + 1] = HEX_CHARS[b & 15];
        }

        return chars;
    }
}

以上為源代碼,看到有個靜態final的常量MD5_ALGORITHM_NAME和許多靜態方法

常用的是其中的md5DigestAsHex方法,它是個靜態方法;

例如密碼是123456,要獲得其加密后的密碼:
DigestUtils.md5DigestAsHex("123456");

為了使得產生更獨特的結果,常常在密碼之前后之后加上“鹽”;

String slat = "luan_ma";
DigestUtils.md5DigestAsHex("123456" + slat);

以下是在一個項目中用到md5加密的方法:

//獲取當前時間和證書
    public Map<String, String> getTime() {
        Map<String, String> map = new HashMap<String, String>();
        String nowDate = DateUtil.format(new Date(), DateUtil.DATE_TIME_PATTERN);
        String date = nowDate.substring(0, 10);
        String time = nowDate.substring(11);
        String certificationKey = key + date + time;
        String md5Password = DigestUtils.md5DigestAsHex(certificationKey.getBytes());
        map.put("md5Password", md5Password);
        map.put("time", time);
        return map;
    }

 這樣就對傳遞的參數按照規定的方式進行了md5加密

 

參考原文:https://www.liaoxuefeng.com/wiki/1252599548343744/1304227729113121


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM