首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java MD5 加密實戰:從基礎雜湊到帶鹽驗證的完整方案

     編輯:Java 相關知識

MD5 雖然不再適合高安全性場景(如密碼儲存應用 bcrypt/Argon2),但在檔案完整性校驗、非核心業務簽名、快速指紋生成等領域仍廣泛使用。本文從基礎用法進階到工業級「加鹽驗證」完整實作。

一、基礎用法:MessageDigest 單向雜湊

public static String getMD5(String str) {
    try {
        MessageDigest md = MessageDigest.getInstance("MD5");
        md.update(str.getBytes(StandardCharsets.UTF_8));
        // 位元組陣列轉 16 進位字串
        return bytesToHex(md.digest());
    } catch (NoSuchAlgorithmException e) {
        throw new RuntimeException("MD5 算法不存在", e);
    }
}

private static String bytesToHex(byte[] bytes) {
    StringBuilder sb = new StringBuilder();
    for (byte b : bytes) {
        sb.append(String.format("%02x", b));
    }
    return sb.toString();
}

缺點:相同輸入產生相同輸出,易受彩虹表攻擊;無鹽值,無法防禦預計算攻擊。

二、進階方案:MyMD5Util 加鹽驗證完整實作

核心思想:隨機鹽值 + 密碼 → MD5 雜湊,儲存格式為 鹽值(12 bytes) + 雜湊值(16 bytes) 的 16 進位字串。

核心工具類

public class MyMD5Util {
    private static final String HEX = "0123456789ABCDEF";
    private static final int SALT_LENGTH = 12; // 12 bytes = 96 bits
    private static final SecureRandom RANDOM = new SecureRandom();

    /** 16 進位字串 → 位元組陣列 */
    public static byte[] hexToBytes(String hex) {
        byte[] result = new byte[hex.length() / 2];
        for (int i = 0; i < result.length; i++) {
            int high = HEX.indexOf(hex.charAt(i * 2));
            int low = HEX.indexOf(hex.charAt(i * 2 + 1));
            result[i] = (byte) ((high << 4) | low);
        }
        return result;
    }

    /** 位元組陣列 → 大寫 16 進位字串 */
    public static String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder(bytes.length * 2);
        for (byte b : bytes) {
            sb.append(HEX.charAt((b >> 4) & 0xF));
            sb.append(HEX.charAt(b & 0xF));
        }
        return sb.toString();
    }

    /** 產生加密密碼:隨機鹽 + 密碼 → 雜湊,返回「鹽+雜湊」16 進位字串 */
    public static String getEncryptedPwd(String password) {
        try {
            byte[] salt = new byte[SALT_LENGTH];
            RANDOM.nextBytes(salt);

            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(salt);
            md.update(password.getBytes(StandardCharsets.UTF_8));
            byte[] digest = md.digest();

            // 組合:鹽(前12) + 雜湊(後16)
            byte[] combined = new byte[SALT_LENGTH + digest.length];
            System.arraycopy(salt, 0, combined, 0, SALT_LENGTH);
            System.arraycopy(digest, 0, combined, SALT_LENGTH, digest.length);

            return bytesToHex(combined);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("MD5 算法不存在", e);
        }
    }

    /** 驗證密碼:從資料庫值提取鹽,重新計算雜湊比對 */
    public static boolean validPassword(String password, String dbHex) {
        try {
            byte[] dbBytes = hexToBytes(dbHex);
            byte[] salt = Arrays.copyOfRange(dbBytes, 0, SALT_LENGTH);
            byte[] dbDigest = Arrays.copyOfRange(dbBytes, SALT_LENGTH, dbBytes.length);

            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(salt);
            md.update(password.getBytes(StandardCharsets.UTF_8));
            byte[] calcDigest = md.digest();

            return MessageDigest.isEqual(calcDigest, dbDigest); // 定時比較,防時序攻擊
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("MD5 算法不存在", e);
        }
    }
}

測試客戶端

public class Client {
    private static final Map<String, String> userDb = new ConcurrentHashMap<>();

    public static void main(String[] args) {
        register("user1", "123456");
        register("user2", "abcdef");

        System.out.println(login("user1", "123456") ? "登入成功" : "密碼錯誤");
        System.out.println(login("user1", "wrong") ? "登入成功" : "密碼錯誤");
    }

    static void register(String user, String pwd) {
        userDb.put(user, MyMD5Util.getEncryptedPwd(pwd));
    }

    static boolean login(String user, String pwd) {
        String dbPwd = userDb.get(user);
        return dbPwd != null && MyMD5Util.validPassword(pwd, dbPwd);
    }
}

三、關鍵安全細節解析

細節做法目的
隨機鹽值SecureRandom 產生 12 bytes相同密碼產生不同雜湊,防禦彩虹表
鹽值儲存前置於雜湊值前,同欄位儲存無需額外欄位,驗證時自動提取
定時比較MessageDigest.isEqual()防止時序側通道攻擊
編碼指定StandardCharsets.UTF_8跨平台一致性,避免預設編碼差異
異常處理包裝為 RuntimeException簡化呼叫端,算法不存在屬系統級錯誤

四、生產環境重要建議

⚠️ 重要提醒:MD5 已知存在碰撞弱點,嚴禁用於高安全性密碼儲存。生產環境請使用:
  • bcrypt(BCrypt.hashpw(),內建鹽值、可調成本因子)
  • Argon2(2015 密碼雜湊大賽冠軍,抗 GPU 破解)
  • PBKDF2(NIST 標準,SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"))
本文方案僅供學習「加鹽驗證」核心模式理解,或用於低風險場景(如 API 請求簽名、快取鍵生成)。

掌握「鹽值生成 → 混合運算 → 組合儲存 → 提取驗證」這一完整流程,是理解現代密碼學工程實踐的基石。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved