本文提供一個生產就緒的 EncryptUtil,涵蓋單向雜湊 (MD5)、鍵控雜湊 (HMAC-SHA1)、可逆混淆 (XOR)、十六進制/Base64 編碼轉換,並解析每種算法的適用場景、安全性與效能特徵。
package com.ompa.common.utils;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public class EncryptUtil {
// ===== 常量配置 =====
private static final String MD5_ALGORITHM = "MD5";
private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1";
private static final String CHARSET = StandardCharsets.UTF_8.name();
// 生產環境應從配置中心/環境變量讀取,不可硬編碼!
private static final String HMAC_KEY = System.getenv().getOrDefault("HMAC_SHA1_KEY", "default_secret_key_change_me");
private static final String XOR_KEY = System.getenv().getOrDefault("XOR_OBFUSCATE_KEY", "t");
// ===== 十六進制編碼/解碼 =====
/**
* 字節數組轉小寫十六進制字串 (高效查表法)
*/
public static String bytesToHex(byte[] bytes) {
if (bytes == null) return "";
char[] hexChars = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
char[] result = new char[bytes.length * 2];
for (int i = 0, j = 0; i < bytes.length; i++) {
int v = bytes[i] & 0xFF;
result[j++] = hexChars[v >>> 4];
result[j++] = hexChars[v & 0x0F];
}
return new String(result);
}
/**
* 十六進制字串轉字節數組
*/
public static byte[] hexToBytes(String hex) {
if (hex == null || hex.length() % 2 != 0) return new byte[0];
byte[] bytes = new byte[hex.length() / 2];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte) Integer.parseInt(hex.substring(i*2, i*2+2), 16);
}
return bytes;
}
// ===== MD5 系列 =====
/**
* 標準 MD5 雜湊 (32 位十六進制)
* @param input 輸入字串
* @return 32 位小寫十六進制 MD5
*/
public static String md5(String input) {
return md5(input.getBytes(StandardCharsets.UTF_8));
}
public static String md5(byte[] input) {
try {
MessageDigest md = MessageDigest.getInstance(MD5_ALGORITHM);
byte[] digest = md.digest(input);
return bytesToHex(digest);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("MD5 algorithm not available", e);
}
}
/**
* 雙重 MD5 (md5(md5(input))) - 早期防彩虹表常用,現不建議作為密碼存儲
*/
public static String doubleMd5(String input) {
return md5(md5(input));
}
// ===== HMAC-SHA1 (鍵控雜湊) =====
/**
* HMAC-SHA1 簽名
* @param data 待簽名數據
* @param key 密鑰
* @return 十六進制簽名
*/
public static String hmacSha1(String data, String key) {
try {
SecretKey secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), HMAC_SHA1_ALGORITHM);
Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
mac.init(secretKey);
byte[] result = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
return bytesToHex(result);
} catch (Exception e) {
throw new RuntimeException("HMAC-SHA1 calculation failed", e);
}
}
/**
* 使用預設密鑰的 HMAC-SHA1
*/
public static String hmacSha1(String data) {
return hmacSha1(data, HMAC_KEY);
}
// ===== XOR 可逆混淆 (非加密,僅輕度混淆) =====
/**
* XOR 字符級混淆/還原 (對稱操作)
* @param input 輸入字串
* @param keyChar 单字符密鑰
* @return 混淆後字串 (再次調用可還原)
*/
public static String xorObfuscate(String input, char keyChar) {
if (input == null) return null;
char[] chars = input.toCharArray();
for (int i = 0; i < chars.length; i++) {
chars[i] ^= keyChar;
}
return new String(chars);
}
public static String xorObfuscate(String input) {
return xorObfuscate(input, XOR_KEY.charAt(0));
}
// ===== Base64 編碼 =====
public static String base64Encode(byte[] input) {
return Base64.getEncoder().encodeToString(input);
}
public static String base64Encode(String input) {
return base64Encode(input.getBytes(StandardCharsets.UTF_8));
}
public static byte[] base64Decode(String input) {
return Base64.getDecoder().decode(input);
}
public static String base64DecodeToString(String input) {
return new String(base64Decode(input), StandardCharsets.UTF_8);
}
// ===== 測試主方法 =====
public static void main(String[] args) {
String test = "123456";
System.out.println("原文: " + test);
System.out.println("MD5: " + md5(test));
System.out.println("Double MD5: " + doubleMd5(test));
System.out.println("HMAC-SHA1: " + hmacSha1(test));
System.out.println("XOR 混淆: " + xorObfuscate(test));
System.out.println("XOR 還原: " + xorObfuscate(xorObfuscate(test)));
System.out.println("Base64: " + base64Encode(test));
}
}
| 算法 | 類型 | 可逆性 | 安全強度 | 適用場景 | 不適用場景 |
|---|---|---|---|---|---|
| MD5 | 單向雜湊 | 不可逆 | 低 (碰撞攻擊可行) | 文件完整性校驗、非敏感 ID 生成、緩存 Key | 密碼存儲、簽名驗證、安全關鍵場景 |
| Double MD5 | 單向雜湊 | 不可逆 | 低 (本質仍是 MD5) | 舊系統兼容、簡單防彩虹表 | 任何新系統密碼存儲 |
| HMAC-SHA1 | 鍵控雜湊 | 不可逆 | 中 (SHA-1 理論弱點,HMAC 結構尚可) | API 簽名驗證、Token 生成、消息完整性 | 高安全簽名 (建議 HMAC-SHA256) |
| XOR 混淆 | 可逆混淆 | 可逆 | 極低 (已知明文攻擊秒破) | 配置文件輕度混淆、傳輸簡要反爬蟲、測試數據遮罩 | 任何機密數據保護、密碼存儲、Token 生成 |
| SHA-256 | 單向雜湊 | 不可逆 | 高 (當前推薦標準) | 密碼雜湊 (配合鹽值)、區塊鏈、數字簽名 | 需可逆場景 |
| BCrypt/SCrypt/Argon2 | 密碼雜湊 | 不可逆 | 極高 (可調計算成本) | 用戶密碼存儲首選 | 高頻驗證場景 (計算慢) |
| AES-GCM | 對稱加密 | 可逆 | 高 (認證加密) | 敏感數據存儲、Token 加密、配置文件加密 | 需非對稱場景 |
// 使用 Spring Security BCrypt (內建鹽值、可調成本)
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Component
public class PasswordService {
// strength 10-12 適中,12 以上較慢但更安全
private final PasswordEncoder encoder = new BCryptPasswordEncoder(12);
public String encode(String rawPassword) {
return encoder.encode(rawPassword); // 自動生成鹽值、雜湊
}
public boolean matches(String rawPassword, String encodedPassword) {
return encoder.matches(rawPassword, encodedPassword);
}
}
public class ApiSignUtil {
// 客戶端簽名生成
public static String generateSign(Map params, String secretKey) {
// 1. 參數按 Key 字典序排序
String signContent = params.entrySet().stream()
.filter(e -> e.getValue() != null && !"sign".equals(e.getKey()))
.sorted(Map.Entry.comparingByKey())
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.joining("&"));
// 2. HMAC-SHA256 簽名
return hmacSha256(signContent, secretKey);
}
// 服務端驗證
public static boolean verifySign(Map params, String secretKey) {
String clientSign = params.get("sign");
if (clientSign == null) return false;
String serverSign = generateSign(params, secretKey);
// 使用恆定時間比較防止時序攻擊
return MessageDigest.isEqual(clientSign.getBytes(), serverSign.getBytes());
}
private static String hmacSha256(String data, String key) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
return bytesToHex(mac.doFinal(data.getBytes(StandardCharsets.UTF_8)));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}