MD5(Message Digest Algorithm 5)雖已不適合密碼學安全場景,但在文件完整性校驗、非敏感數據指紋生成、緩存鍵計算等領域仍被廣泛使用。本文提供兩種核心實現方案、生產級工具類封裝及安全使用建議。
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.nio.charset.StandardCharsets;
public class Md5Utils {
/**
* 計算字符串 MD5(十六進制大寫)
* @param input 原始字符串
* @return 32 位十六進制字符串,失敗返回 null
*/
public static String md5Hex(String input) {
if (input == null) return null;
try {
// 1. 獲取 MessageDigest 實例
MessageDigest md = MessageDigest.getInstance("MD5");
// 2. 更新輸入數據(支持分塊更新大文件)
md.update(input.getBytes(StandardCharsets.UTF_8));
// 3. 完成摘要計算
byte[] digest = md.digest();
// 4. 字節數組轉十六進制字符串
return bytesToHexUpper(digest);
} catch (NoSuchAlgorithmException e) {
// JDK 必備算法,理論不會發生
throw new IllegalStateException("MD5 算法不可用", e);
}
}
/**
* 字節數組轉大寫十六進制(高性能位運算)
*/
private static String bytesToHexUpper(byte[] bytes) {
char[] hexDigits = "0123456789ABCDEF".toCharArray();
char[] result = new char[bytes.length * 2];
int index = 0;
for (byte b : bytes) {
result[index++] = hexDigits[(b >>> 4) & 0xF]; // 高 4 位
result[index++] = hexDigits[b & 0xF]; // 低 4 位
}
return new String(result);
}
}
import java.security.MessageDigest;
import java.math.BigInteger;
public class Md5Simple {
public static String md5(String input) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest(input.getBytes(StandardCharsets.UTF_8));
// 1 轉為正數,16 進制,不足 32 位左補 0
return String.format("%032x", new BigInteger(1, digest)).toUpperCase();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
}
性能對比:方案一約快 3-5 倍,避免了 BigInteger 創建與格式化開銷,適合高頻調用。
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class FileMd5 {
/**
* 計算文件 MD5(流式處理,內存常數級)
* @param filePath 文件路徑
* @return MD5 十六進制字符串
*/
public static String fileMd5(String filePath) throws IOException {
try (InputStream is = new FileInputStream(filePath)) {
return streamMd5(is);
}
}
/**
* 計算輸入流 MD5
* @param inputStream 輸入流(調用者負責關閉或使用 try-with-resources)
*/
public static String streamMd5(InputStream inputStream) throws IOException {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] buffer = new byte[8192]; // 8KB 緩衝區
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
md.update(buffer, 0, bytesRead);
}
return Md5Utils.bytesToHexUpper(md.digest());
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
}
}
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.io.InputStream;
import java.io.IOException;
public final class DigestUtils {
private DigestUtils() {} // 工具類防實例化
private static final char[] HEX_UPPER = "0123456789ABCDEF".toCharArray();
private static final char[] HEX_LOWER = "0123456789abcdef".toCharArray();
// ===== 字符串 MD5 =====
public static String md5Hex(String input) {
return md5Hex(input, StandardCharsets.UTF_8, true);
}
public static String md5Hex(String input, Charset charset) {
return md5Hex(input, charset, true);
}
public static String md5Hex(String input, boolean upperCase) {
return md5Hex(input, StandardCharsets.UTF_8, upperCase);
}
public static String md5Hex(String input, Charset charset, boolean upperCase) {
if (input == null) return null;
return bytesToHex(md5Bytes(input.getBytes(charset)), upperCase);
}
// ===== 字節數組 MD5 =====
public static byte[] md5Bytes(byte[] input) {
if (input == null) return null;
try {
MessageDigest md = MessageDigest.getInstance("MD5");
return md.digest(input);
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("MD5 not available", e);
}
}
// ===== 文件/流 MD5 =====
public static String md5File(String filePath) throws IOException {
try (InputStream is = new java.io.FileInputStream(filePath)) {
return md5Stream(is);
}
}
public static String md5Stream(InputStream is) throws IOException {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] buffer = new byte[8192];
int read;
while ((read = is.read(buffer)) != -1) {
md.update(buffer, 0, read);
}
return bytesToHex(md.digest(), true);
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
}
// ===== 核心轉換 =====
private static String bytesToHex(byte[] bytes, boolean upperCase) {
if (bytes == null) return null;
char[] hex = upperCase ? HEX_UPPER : HEX_LOWER;
char[] result = new char[bytes.length * 2];
int idx = 0;
for (byte b : bytes) {
result[idx++] = hex[(b >>> 4) & 0xF];
result[idx++] = hex[b & 0xF];
}
return new String(result);
}
}
@Component
public class SignatureService {
/**
* 生成 API 簽名:參數排序 + 密鑰 + MD5
*/
public String generateSign(Map params, String secretKey) {
// 1. 參數按鍵名 ASCII 碼排序
StringBuilder sb = new StringBuilder();
params.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.forEach(e -> sb.append(e.getKey()).append('=').append(e.getValue()).append('&'));
// 2. 拼接密鑰
sb.append("key=").append(secretKey);
// 3. MD5 大寫
return DigestUtils.md5Hex(sb.toString(), true);
}
/**
* 驗證簽名
*/
public boolean verifySign(Map params, String secretKey, String sign) {
String expected = generateSign(params, secretKey);
return expected.equalsIgnoreCase(sign);
}
}
| 場景 | MD5 適用性 | 推薦替代 |
|---|---|---|
| 密碼存儲 | ❌ 極不安全(彩虹表、GPU 破解) | BCrypt、PBKDF2、Argon2、Scrypt |
| Token 簽名 | ❌ 可碰撞偽造 | HMAC-SHA256、RSA/ECDSA 簽名、JWT |
| 文件完整性校驗 | ⚠️ 可接受(非惡意篡改場景) | SHA-256、SHA-3、BLAKE3 |
| 緩存鍵生成 | ✅ 適用(性能好、長度固定) | 同左 |
| 數據去重指紋 | ✅ 適用 | 同左 |
@Component
public class PasswordEncoder {
private final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(12); // 強度 12
public String encode(CharSequence rawPassword) {
return encoder.encode(rawPassword);
}
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return encoder.matches(rawPassword, encodedPassword);
}
}
總結:MD5 仍有其工程價值,但必須明確適用邊界。涉及認證授權、簽名防篡改等安全敏感場景,請堅決使用現代加密算法。工具類封裝應支持多字符集、大小寫、流式處理,滿足不同業務需求。