資料加密是資訊安全基石。本文提供生產級可用的 Java 加密工具類,涵蓋對稱加密(DES/AES)與單向雜湊(MD5/SHA),包含金鑰管理、向量處理、Base64 編碼與異常處理完整實作。
Java 8+ 內建 javax.crypto,無需額外引入。建議優先使用 AES 取代不安全的 DES。
package com.example.util;
import javax.crypto.*;
import javax.crypto.spec.*;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Base64;
public final class CryptoUtils {
private CryptoUtils() {}
// ==================== AES 對稱加密 (推薦) ====================
private static final String AES_ALGO = "AES";
private static final String AES_CBC_PKCS5 = "AES/CBC/PKCS5Padding";
private static final int KEY_SIZE = 256; // 128/192/256
/** 從密碼派生金鑰 (PBKDF2) */
public static SecretKey deriveKey(String password, byte[] salt) throws Exception {
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, KEY_SIZE);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
return new SecretKeySpec(factory.generateSecret(spec).getEncoded(), AES_ALGO);
}
/** AES-CBC 加密 → Base64(salt + iv + ciphertext) */
public static String encryptAES(String plaintext, String password) throws Exception {
byte[] salt = SecureRandom.getInstanceStrong().generateSeed(16);
byte[] iv = SecureRandom.getInstanceStrong().generateSeed(16);
SecretKey key = deriveKey(password, salt);
Cipher cipher = Cipher.getInstance(AES_CBC_PKCS5);
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
byte[] ciphertext = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
// 組合:salt(16) + iv(16) + ciphertext
byte[] combined = new byte[salt.length + iv.length + ciphertext.length];
System.arraycopy(salt, 0, combined, 0, salt.length);
System.arraycopy(iv, 0, combined, salt.length, iv.length);
System.arraycopy(ciphertext, 0, combined, salt.length + iv.length, ciphertext.length);
return Base64.getEncoder().encodeToString(combined);
}
/** AES-CBC 解密 */
public static String decryptAES(String encryptedBase64, String password) throws Exception {
byte[] combined = Base64.getDecoder().decode(encryptedBase64);
if (combined.length < 32) throw new IllegalArgumentException("無效密文長度");
byte[] salt = new byte[16];
byte[] iv = new byte[16];
byte[] ciphertext = new byte[combined.length - 32];
System.arraycopy(combined, 0, salt, 0, 16);
System.arraycopy(combined, 16, iv, 0, 16);
System.arraycopy(combined, 32, ciphertext, 0, ciphertext.length);
SecretKey key = deriveKey(password, salt);
Cipher cipher = Cipher.getInstance(AES_CBC_PKCS5);
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
byte[] plain = cipher.doFinal(ciphertext);
return new String(plain, StandardCharsets.UTF_8);
}
// ==================== MD5 / SHA 雜湊 (單向) ====================
/** MD5 雜湊 (不建議用於密碼儲存,僅校驗完整性) */
public static String md5(String input) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] hash = md.digest(input.getBytes(StandardCharsets.UTF_8));
return bytesToHex(hash);
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("MD5 不可用", e);
}
}
/** SHA-256 雜湊 */
public static String sha256(String input) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hash = md.digest(input.getBytes(StandardCharsets.UTF_8));
return bytesToHex(hash);
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("SHA-256 不可用", e);
}
}
/** 帶鹽 SHA-256 (密碼儲存推薦) */
public static String hashPassword(String password) {
byte[] salt = SecureRandom.getInstanceStrong().generateSeed(16);
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(salt);
byte[] hash = md.digest(password.getBytes(StandardCharsets.UTF_8));
byte[] combined = new byte[salt.length + hash.length];
System.arraycopy(salt, 0, combined, 0, salt.length);
System.arraycopy(hash, 0, combined, salt.length, hash.length);
return Base64.getEncoder().encodeToString(combined);
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("SHA-256 不可用", e);
}
}
/** 驗證帶鹽密碼 */
public static boolean verifyPassword(String password, String storedBase64) {
byte[] combined = Base64.getDecoder().decode(storedBase64);
if (combined.length < 16) return false;
byte[] salt = new byte[16];
byte[] hash = new byte[combined.length - 16];
System.arraycopy(combined, 0, salt, 0, 16);
System.arraycopy(combined, 16, hash, 0, hash.length);
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(salt);
byte[] calc = md.digest(password.getBytes(StandardCharsets.UTF_8));
return MessageDigest.isEqual(hash, calc);
} catch (NoSuchAlgorithmException e) {
return false;
}
}
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) sb.append(String.format("%02x", b));
return sb.toString();
}
// ==================== 測試主程式 ====================
public static void main(String[] args) throws Exception {
String pwd = "MySecret@2024";
String data = "敏感業務資料:訂單號 ORD-20240115-001";
// AES 加解密
String enc = encryptAES(data, pwd);
System.out.println("AES加密: " + enc);
System.out.println("AES解密: " + decryptAES(enc, pwd));
// 密碼雜湊儲存與驗證
String hashed = hashPassword(pwd);
System.out.println("密碼雜湊: " + hashed);
System.out.println("驗證成功: " + verifyPassword(pwd, hashed));
System.out.println("驗證失敗: " + verifyPassword("WrongPwd", hashed));
// 簡單完整性校驗
System.out.println("MD5: " + md5(data));
System.out.println("SHA-256: " + sha256(data));
}
}
| 場景 | 推薦算法 | 禁用/避免 |
|---|---|---|
| 資料加密傳輸/儲存 | AES-256-CBC / GCM | DES, 3DES, ECB 模式 |
| 密碼儲存 | PBKDF2 / bcrypt / Argon2 | MD5, SHA-1, 純 SHA-256 無鹽 |
| 檔案完整性校驗 | SHA-256 / SHA-3 | MD5 (碰撞風險) |
| API 簽名 | HMAC-SHA256 | 自定義拼接簽名 |
SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256") 替代舊版 PBKDF2WithHmacSHA1MessageDigest.isEqual() 恆時間比對防時序攻擊本工具類提供開箱即用的 AES 加解密與密碼雜湊功能,採用 PBKDF2 派生金鑰、隨機 Salt/IV、Base64 封裝,符合現代密碼學最佳實踐。生產環境請配合金鑰管理系統、審計日誌與合規審查使用。