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

Java 實現 SHA-1 與 MD5 加密算法完整指南

     編輯:Java 相關知識

消息摘要算法概覽

算法輸出長度安全性性能推薦用途
MD5128 位 (16 字節 / 32 十六進制)已破解 (碰撞攻擊)極快非安全場景:文件校驗、緩存 Key、去重指紋
SHA-1160 位 (20 字節 / 40 十六進制)已破解 (選定前綴碰撞)快非安全場景:Git 提交 ID、版本控制、非敏感完整性校驗
SHA-256256 位 (32 字節 / 64 十六進制)安全中等安全場景:數字簽名、證書、區塊鏈、密碼存儲 (配合鹽)
SHA-512512 位 (64 字節 / 128 十六進制)安全較慢高安全需求、長期有效性

核心 API:MessageDigest

MessageDigest md = MessageDigest.getInstance("MD5"); // 或 "SHA-1", "SHA-256"
md.update(dataBytes);      // 分塊更新
md.update(moreBytes);
byte[] digest = md.digest(); // 完成計算,返回字節數組
// 或一次性
byte[] digest = MessageDigest.getInstance("SHA-256").digest(dataBytes);

完整工具類實現

package com.util.crypto;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class DigestUtils {
    
    private static final char[] HEX_CHARS = "0123456789abcdef".toCharArray();
    
    /** MD5 摘要 (32 位小寫十六進制) */
    public static String md5(String input) {
        return digest("MD5", input.getBytes(StandardCharsets.UTF_8));
    }
    
    public static String md5(byte[] input) {
        return digest("MD5", input);
    }
    
    /** SHA-1 摘要 (40 位小寫十六進制) */
    public static String sha1(String input) {
        return digest("SHA-1", input.getBytes(StandardCharsets.UTF_8));
    }
    
    public static String sha1(byte[] input) {
        return digest("SHA-1", input);
    }
    
    /** SHA-256 摘要 (64 位小寫十六進制) */
    public static String sha256(String input) {
        return digest("SHA-256", input.getBytes(StandardCharsets.UTF_8));
    }
    
    public static String sha256(byte[] input) {
        return digest("SHA-256", input);
    }
    
    /** 通用摘要計算 */
    private static String digest(String algorithm, byte[] input) {
        try {
            MessageDigest md = MessageDigest.getInstance(algorithm);
            byte[] hash = md.digest(input);
            return bytesToHex(hash);
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException("JVM 缺少算法: " + algorithm, e);
        }
    }
    
    /** 字節數組轉十六進制字符串 (高性能) */
    private static String bytesToHex(byte[] bytes) {
        char[] hex = new char[bytes.length * 2];
        for (int i = 0; i < bytes.length; i++) {
            int b = bytes[i] & 0xFF;
            hex[i * 2] = HEX_CHARS[b >>> 4];
            hex[i * 2 + 1] = HEX_CHARS[b & 0x0F];
        }
        return new String(hex);
    }
    
    /** 文件摘要計算 (大文件分塊讀取) */
    public static String digestFile(String algorithm, String filePath) {
        try (java.io.FileInputStream fis = new java.io.FileInputStream(filePath)) {
            MessageDigest md = MessageDigest.getInstance(algorithm);
            byte[] buffer = new byte[8192];
            int len;
            while ((len = fis.read(buffer)) != -1) {
                md.update(buffer, 0, len);
            }
            return bytesToHex(md.digest());
        } catch (Exception e) {
            throw new RuntimeException("文件摘要計算失敗: " + filePath, e);
        }
    }
    
    /** HMAC-SHA256 (需密鑰) */
    public static String hmacSha256(String data, String key) {
        try {
            javax.crypto.Mac mac = javax.crypto.Mac.getInstance("HmacSHA256");
            mac.init(new javax.crypto.spec.SecretKeySpec(
                key.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
            return bytesToHex(mac.doFinal(data.getBytes(StandardCharsets.UTF_8)));
        } catch (Exception e) {
            throw new RuntimeException("HMAC 計算失敗", e);
        }
    }
}

密碼存儲最佳實踐:鹽值 + 慢哈希

嚴禁直接使用 MD5/SHA-1 存儲密碼! 必須使用專門的慢哈希算法:

BCrypt (推薦,Spring Security 默認)

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-crypto</artifactId>
    <version>5.7.x</version>
</dependency>

@Component
public class PasswordEncoder {
    private final org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder encoder 
        = new org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder(12); // strength 12
    
    public String encode(String rawPassword) {
        return encoder.encode(rawPassword); // 自動生成鹽值、迭代 2^12 次
    }
    
    public boolean matches(String rawPassword, String encodedPassword) {
        return encoder.matches(rawPassword, encodedPassword);
    }
}

PBKDF2 (標準化、可配置迭代次數)

public class Pbkdf2Encoder {
    private static final int ITERATIONS = 100000;
    private static final int KEY_LENGTH = 256;
    private static final String ALGORITHM = "PBKDF2WithHmacSHA256";
    
    public String encode(String password) {
        try {
            byte[] salt = new byte[16];
            new java.security.SecureRandom().nextBytes(salt);
            
            javax.crypto.SecretKeyFactory factory = 
                javax.crypto.SecretKeyFactory.getInstance(ALGORITHM);
            javax.crypto.spec.PBEKeySpec spec = 
                new javax.crypto.spec.PBEKeySpec(password.toCharArray(), salt, ITERATIONS, KEY_LENGTH);
            byte[] hash = factory.generateSecret(spec).getEncoded();
            
            // 格式: algorithm$iterations$salt$hash (Base64)
            return String.format("%s$%d$%s$%s", 
                ALGORITHM, ITERATIONS, 
                Base64.getEncoder().encodeToString(salt),
                Base64.getEncoder().encodeToString(hash));
        } catch (Exception e) {
            throw new RuntimeException("PBKDF2 編碼失敗", e);
        }
    }
    
    public boolean matches(String password, String encoded) {
        String[] parts = encoded.split("\\$");
        if (parts.length != 4) return false;
        int iterations = Integer.parseInt(parts[1]);
        byte[] salt = Base64.getDecoder().decode(parts[2]);
        byte[] hash = Base64.getDecoder().decode(parts[3]);
        
        try {
            javax.crypto.SecretKeyFactory factory = 
                javax.crypto.SecretKeyFactory.getInstance(parts[0]);
            javax.crypto.spec.PBEKeySpec spec = 
                new javax.crypto.spec.PBEKeySpec(password.toCharArray(), salt, iterations, hash.length * 8);
            byte[] testHash = factory.generateSecret(spec).getEncoded();
            return MessageDigest.isEqual(hash, testHash); // 恆時比較
        } catch (Exception e) {
            return false;
        }
    }
}

Argon2 (現代、抗 GPU/ASIC、內存難度可調)

<dependency>
    <groupId>de.mkammerer</groupId>
    <artifactId>argon2-jvm</artifactId>
    <version>2.11</version>
</dependency>

@Component
public class Argon2Encoder {
    private final de.mkammerer.argon2.Argon2 argon2 = 
        de.mkammerer.argon2.Argon2Factory.create(
            de.mkammerer.argon2.Argon2Factory.Argon2Types.ARGON2id);
    
    public String encode(String password) {
        // 迭代 3 次、內存 65536 KB、並行度 4
        return argon2.hash(3, 65536, 4, password.toCharArray());
    }
    
    public boolean matches(String password, String hash) {
        return argon2.verify(hash, password.toCharArray());
    }
}

文件完整性校驗場景

public class FileIntegrityChecker {
    public static void main(String[] args) {
        String file = "dist/app-release.apk";
        
        // 發布時計算並公布
        String sha256 = DigestUtils.digestFile("SHA-256", file);
        String md5 = DigestUtils.digestFile("MD5", file);
        System.out.println("SHA-256: " + sha256);
        System.out.println("MD5: " + md5);
        
        // 用戶下載後驗證
        String userSha256 = DigestUtils.digestFile("SHA-256", "downloads/app-release.apk");
        if (sha256.equalsIgnoreCase(userSha256)) {
            System.out.println("文件完整性驗證通過");
        } else {
            System.err.println("文件已損壞或被篡改!");
        }
    }
}

緩存 Key 生成 / 去重指紋

public class CacheKeyGenerator {
    // 將請求參數序列化為 MD5 作為 Redis Key
    public static String generateKey(Object... params) {
        StringBuilder sb = new StringBuilder();
        for (Object p : params) {
            sb.append(p == null ? "null" : p.toString()).append("|");
        }
        return "cache:" + DigestUtils.md5(sb.toString());
    }
    
    // 文件去重:上傳前計算 SHA-256,查詢數據庫是否存在
    public static String fileFingerprint(byte[] content) {
        return DigestUtils.sha256(content);
    }
}

數字簽名場景 (SHA256withRSA)

public class DigitalSignature {
    // 簽名
    public static String sign(String data, java.security.PrivateKey privateKey) {
        try {
            java.security.Signature sig = java.security.Signature.getInstance("SHA256withRSA");
            sig.initSign(privateKey);
            sig.update(data.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(sig.sign());
        } catch (Exception e) {
            throw new RuntimeException("簽名失敗", e);
        }
    }
    
    // 驗證
    public static boolean verify(String data, String signature, java.security.PublicKey publicKey) {
        try {
            java.security.Signature sig = java.security.Signature.getInstance("SHA256withRSA");
            sig.initVerify(publicKey);
            sig.update(data.getBytes(StandardCharsets.UTF_8));
            return sig.verify(Base64.getDecoder().decode(signature));
        } catch (Exception e) {
            return false;
        }
    }
}

性能優化建議

  1. 復用 MessageDigest:線程局部緩存或對象池 (注意 reset())
  2. 避免重複字符串獲取字節:傳入 byte[] 而非 String
  3. 大文件分塊讀取:8KB-64KB 緩衝區,避免 OOM
  4. 硬件加速:JVM 參數 -XX:+UseSHA、-XX:+UseSHA256Intrinsics (x86 AES-NI/SHA 指令集)

常見誤區避坑

誤區後果正確做法
MD5/SHA1 存儲密碼彩虹表破解、GPU 爆破、合規風險使用 BCrypt/PBKDF2/Argon2 + 隨機鹽
無鹽值哈希相同密碼哈希相同、批量破解每用戶唯一隨機鹽值 (16+ 字節)
單次快速哈希極易暴力破解慢哈希:迭代 10萬+ 次、內存難度
自定義加密組合安全性未經驗證、副作用未知使用標準庫、標準算法、標準參數
Base64 當加密可逆編碼、零安全性Base32/64 僅用於二進制文本化傳輸

Java 通過 MessageDigest、Mac、Signature、SecretKeyFactory 提供完整的密碼學原語支持。選擇算法的核心原則:密碼存儲用慢哈希 (BCrypt/Argon2),完整性校驗用 SHA-256,非安全指紋用 MD5,數字簽名用 SHA256withRSA/ECDSA。嚴禁在安全敏感場景使用已破解算法,並始終遵循「標準算法、標準庫、標準參數」原則。

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