| 算法 | 輸出長度 | 安全性 | 性能 | 推薦用途 |
|---|---|---|---|---|
| MD5 | 128 位 (16 字節 / 32 十六進制) | 已破解 (碰撞攻擊) | 極快 | 非安全場景:文件校驗、緩存 Key、去重指紋 |
| SHA-1 | 160 位 (20 字節 / 40 十六進制) | 已破解 (選定前綴碰撞) | 快 | 非安全場景:Git 提交 ID、版本控制、非敏感完整性校驗 |
| SHA-256 | 256 位 (32 字節 / 64 十六進制) | 安全 | 中等 | 安全場景:數字簽名、證書、區塊鏈、密碼存儲 (配合鹽) |
| SHA-512 | 512 位 (64 字節 / 128 十六進制) | 安全 | 較慢 | 高安全需求、長期有效性 |
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 存儲密碼! 必須使用專門的慢哈希算法:
<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);
}
}
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;
}
}
}
<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("文件已損壞或被篡改!");
}
}
}
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);
}
}
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;
}
}
}
reset())byte[] 而非 String-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。嚴禁在安全敏感場景使用已破解算法,並始終遵循「標準算法、標準庫、標準參數」原則。