AES 關鍵參數選擇
- 模式:CBC(Cipher Block Chaining),需初始化向量(IV),比 ECB 安全。
- 填充:PKCS7Padding(Java 稱 PKCS5Padding),區塊大小 16 位元組。
- 金鑰長度:128/192/256 位。本例用 SHA-256 摘要種子取前 16 位元組作 128-bit 金鑰。
- IV:生產環境必須隨機生成並隨密文儲存/傳輸,本例為示範固定全 0。
完整工具類
import javax.crypto.*;
import javax.crypto.spec.*;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public final class AesCrypt {
private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
private static final String SEED = "U1MjU1M0FDOUZ.Qz"; // 16 字元種子,實際應從配置/密碼管理讀取
private static final byte[] FIXED_IV = new byte[16]; // 僅示範,生產請用 SecureRandom 生成
private final SecretKeySpec keySpec;
private final IvParameterSpec ivSpec;
public AesCrypt() throws NoSuchAlgorithmException {
// 衍生金鑰:SHA-256(SEED) 取前 16 bytes
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(SEED.getBytes(StandardCharsets.UTF_8));
byte[] keyBytes = new byte[16];
System.arraycopy(hash, 0, keyBytes, 0, 16);
this.keySpec = new SecretKeySpec(keyBytes, "AES");
this.ivSpec = new IvParameterSpec(FIXED_IV);
}
public String encrypt(String plainText) {
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
byte[] encrypted = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encrypted);
} catch (Exception e) {
throw new RuntimeException("加密失敗", e);
}
}
public String decrypt(String cipherText) {
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
byte[] decoded = Base64.getDecoder().decode(cipherText);
byte[] decrypted = cipher.doFinal(decoded);
return new String(decrypted, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new RuntimeException("解密失敗", e);
}
}
// 測試
public static void main(String[] args) throws Exception {
AesCrypt crypt = new AesCrypt();
String original = "敏感資料:身分證 A123456789";
String encrypted = crypt.encrypt(original);
String decrypted = crypt.decrypt(encrypted);
System.out.println("原文:" + original);
System.out.println("密文:" + encrypted);
System.out.println("解密:" + decrypted);
}
}
生產環境必做的強化
- 隨機 IV:每次加密生成新 IV(
SecureRandom),將 IV(16 bytes)拼接在密文前端,解密時切割取出。
// 加密時
byte[] iv = new byte[16]; new SecureRandom().nextBytes(iv);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(iv));
byte[] cipherBytes = cipher.doFinal(plainBytes);
byte[] output = new byte[iv.length + cipherBytes.length];
System.arraycopy(iv, 0, output, 0, iv.length);
System.arraycopy(cipherBytes, 0, output, iv.length, cipherBytes.length);
return Base64.getEncoder().encodeToString(output);
// 解密時
byte[] input = Base64.getDecoder().decode(cipherText);
byte[] iv = Arrays.copyOfRange(input, 0, 16);
byte[] cipherBytes = Arrays.copyOfRange(input, 16, input.length);
// ... 用 iv 初始化 IvParameterSpec 解密
- 金鑰管理:勿硬編碼,使用 Vault、AWS KMS、環境變數、JCEKS keystore。
- 完整性驗證:AES-CBC 無完整性保護,建議改用 AES-GCM(Authenticated Encryption)或額外加 HMAC-SHA256。
- 例外處理:區分
BadPaddingException(密文/金鑰錯誤)、IllegalBlockSizeException 資料損壞,避免洩露細節。
常見坑點
PKCS5Padding 在 Java 中實際支援 16 位元組區塊(AES),名稱歷史原因。
Base64 編碼/解碼需指定 StandardCharsets.UTF_8,避免平台預設編碼差異。
- Android 使用
android.util.Base64,JRE 用 java.util.Base64(Java 8+)或 Apache Commons Codec。
線上工具驗證