Aes256Utils 是一個提供 AES-256 加密和解密功能的工具類別,使用 CBC 模式和 PKCS5Padding 填充。此類別設計為靜態工具類別,不允許建立實例。
// 基本加密(使用預設金鑰)
String encrypted = Aes256Utils.encryptAES256(plainText);
// 使用自定義金鑰加密
String encrypted = Aes256Utils.encryptAES256(plainText, passwordKey);
// 使用自定義金鑰和 Salt 加密
String encrypted = Aes256Utils.encryptAES256(plainText, passwordKey, salt);
// 完整參數加密
String encrypted = Aes256Utils.encryptAES256(plainText, passwordKey, salt, stringIv);
// 基本解密(使用預設金鑰)
String decrypted = Aes256Utils.decryptAES256(encryptedText);
// 使用自定義金鑰解密
String decrypted = Aes256Utils.decryptAES256(encryptedText, passwordKey);
// 使用自定義金鑰和 Salt 解密
String decrypted = Aes256Utils.decryptAES256(encryptedText, passwordKey, salt);
// 完整參數解密
String decrypted = Aes256Utils.decryptAES256(encryptedText, passwordKey, salt, stringIv);
// 產生新的 IV
byte[] iv = Aes256Utils.generateAes256ByteIv();
環境設定
AES256_PASSWORD_KEY:加密金鑰AES256_IV_KEY:初始化向量AES256_SALT_KEY:Salt 值異常處理
使用時需要處理以下可能的異常:
IOException
NoSuchAlgorithmException
InvalidKeySpecException
NoSuchPaddingException
InvalidAlgorithmParameterException
InvalidKeyException
BadPaddingException
IllegalBlockSizeException
安全考量
這些測試案例展示了以下功能的正確性:
@Test
void testBasicEncryptDecrypt() {
String plainText = "Hello, World!";
// 使用預設金鑰加密
String encrypted = Aes256Utils.encryptAES256(plainText);
assertNotNull(encrypted, "加密結果不應為 null");
assertNotEquals(plainText, encrypted, "加密後的文字不應與原文相同");
// 解密並驗證
String decrypted = Aes256Utils.decryptAES256(encrypted);
assertEquals(plainText, decrypted, "解密後應該與原文相同");
}
@Test
void testCustomKeyEncryptDecrypt() {
String plainText = "測試中文文字";
String customKey = "MySecretKey123!@#";
String customSalt = "CustomSalt";
String customIv = Base64.getEncoder().encodeToString(
Aes256Utils.generateAes256ByteIv());
// 使用自定義參數加密
String encrypted = Aes256Utils.encryptAES256(
plainText, customKey, customSalt, customIv);
assertNotNull(encrypted, "加密結果不應為 null");
// 使用相同參數解密
String decrypted = Aes256Utils.decryptAES256(
encrypted, customKey, customSalt, customIv);
assertEquals(plainText, decrypted, "使用自定義參數解密後應與原文相同");
// 使用不同金鑰解密(應該失敗)
assertThrows(Exception.class, () -> {
Aes256Utils.decryptAES256(encrypted, "WrongKey", customSalt, customIv);
}, "使用錯誤的金鑰應該拋出異常");
}
@Test
void testIvGeneration() {
// 測試 IV 生成
byte[] iv1 = Aes256Utils.generateAes256ByteIv();
byte[] iv2 = Aes256Utils.generateAes256ByteIv();
assertNotNull(iv1, "生成的 IV 不應為 null");
assertEquals(16, iv1.length, "IV 長度應為 16 位元組");
assertFalse(Arrays.equals(iv1, iv2), "連續生成的 IV 應該不同");
}
@Test
void testExceptionHandling() {
String plainText = "Test Data";
// 測試 null 輸入
assertThrows(IllegalArgumentException.class, () -> {
Aes256Utils.encryptAES256(null);
}, "null 輸入應拋出異常");
// 測試空字串輸入
assertThrows(IllegalArgumentException.class, () -> {
Aes256Utils.encryptAES256("");
}, "空字串輸入應拋出異常");
// 測試無效的 Base64 輸入
assertThrows(IllegalArgumentException.class, () -> {
Aes256Utils.decryptAES256("InvalidBase64");
}, "無效的 Base64 輸入應拋出異常");
}
基本加密解密測試
自定義參數加密解密測試
IV 生成測試
異常處理測試
測試環境準備
測試資料安全性
package tw.lewishome.webapp.base.utility.common;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Base64;
import java.util.Map;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.lang3.StringUtils;
import tw.lewishome.webapp.GlobalConstants;
/**
* AES256 加密解密工具類別
* 此類別提供 AES-256 加密和解密功能,使用 CBC 模式和 PKCS5Padding 填充。
*
* 主要功能:
* - AES-256 加密:將明文字串加密為密文
* - AES-256 解密:將密文解密為原始明文
* - 初始化向量(IV)生成:產生用於加解密的 IV
*
* 安全特性:
* - 使用 PBKDF2WithHmacSHA256 進行金鑰衍生
* - 支援自定義 Salt 值增加安全性
* - 使用 CBC 模式提供更好的安全性
* - 支援 256 位元金鑰長度
*
* 使用須知:
* - 此類別為工具類別,所有方法皆為靜態方法
* - 不允許建立此類別的實例
* - 加解密過程中需要相同的密碼、Salt 和 IV 才能正確運作
* - 預設會使用系統環境變數中的金鑰和 IV
*
* 異常處理:
* - 所有方法都可能拋出加解密相關的異常
* - 使用時需要適當的異常處理機制
*
* @author Lewis
* @version 1.0
*/
public class Aes256Utils {
/** Private constructor to prevent instantiation */
private Aes256Utils() {
throw new IllegalStateException("This is a utility class and cannot be instantiated");
}
// =================== AES256 Decrypt Method ===================//
/**
* AES256 String 解壓縮(解密) (Default Public Key)
*
* @param encryptText a String object
* @return decryptAES256 String object
* @throws IOException IOException IOException
* @throws NoSuchAlgorithmException NoSuchAlgorithmException
* @throws InvalidKeySpecException InvalidKeySpecException
* @throws NoSuchPaddingException NoSuchPaddingException
* @throws InvalidAlgorithmParameterException InvalidAlgorithmParameterException
* @throws InvalidKeyException InvalidKeyException
* @throws BadPaddingException BadPaddingException
* @throws IllegalBlockSizeException IllegalBlockSizeException
*/
public static String decryptAES256(String encryptText)
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, IOException,
InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
String slat = getDefaultSlat();
byte[] byteIv = getDefaultByteIv();
String stringIv = TypeConvert.bytesToHexString(byteIv);
String passwordKey = GlobalConstants.ENV_VAR.get(GlobalConstants.AES256_PASSWORD_KEY);
if (StringUtils.isBlank(slat)) {
Map<String, String> mapKeyParis = RSAUtils.loadKeyParisString();
passwordKey = mapKeyParis.get(RSAUtils.PUBLIC_KEY);
} else {
passwordKey = RSAUtils.decryptStringByPrivateKey(slat);
}
return decryptAES256(encryptText, passwordKey, slat, stringIv);
}
/**
* AES256 String 解壓縮(解密) (Default Public Key)
*
* @param encryptText a String object
* @param passwordKey a String object
* @return decryptAES256 String object
* @throws IOException IOException IOException
* @throws NoSuchAlgorithmException NoSuchAlgorithmException
* @throws InvalidKeySpecException InvalidKeySpecException
* @throws NoSuchPaddingException NoSuchPaddingException
* @throws InvalidAlgorithmParameterException InvalidAlgorithmParameterException
* @throws InvalidKeyException InvalidKeyException
* @throws BadPaddingException BadPaddingException
* @throws IllegalBlockSizeException IllegalBlockSizeException
*/
public static String decryptAES256(String encryptText, String passwordKey)
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, IOException,
InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
String slat = getDefaultSlat();
byte[] byteIv = getDefaultByteIv();
String stringIv = TypeConvert.bytesToHexString(byteIv);
return decryptAES256(encryptText, passwordKey, slat, stringIv);
}
/**
* AES256 String 解壓縮(解密) (Default Public Key)
*
* @param encryptText a String object
* @param passwordKey a String object
* @param slat a String object
* @return decryptAES256 String object
* @throws IOException IOException IOException
* @throws NoSuchAlgorithmException NoSuchAlgorithmException
* @throws InvalidKeySpecException InvalidKeySpecException
* @throws NoSuchPaddingException NoSuchPaddingException
* @throws InvalidAlgorithmParameterException InvalidAlgorithmParameterException
* @throws InvalidKeyException InvalidKeyException
* @throws BadPaddingException BadPaddingException
* @throws IllegalBlockSizeException IllegalBlockSizeException
*/
public static String decryptAES256(String encryptText, String passwordKey, String slat)
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, IOException,
InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
byte[] byteIv = getDefaultByteIv();
String stringIv = TypeConvert.bytesToHexString(byteIv);
return decryptAES256(encryptText, passwordKey, slat, stringIv);
}
/**
* AES256 String 解壓縮(解密) (Default Public Key)
*
* @param encryptText a String object
* @param passwordKey a String object
* @param slat a String object
* @param stringIv a String object
* @return decryptAES256 String object
* @throws IOException IOException IOException
* @throws NoSuchAlgorithmException NoSuchAlgorithmException
* @throws InvalidKeySpecException InvalidKeySpecException
* @throws NoSuchPaddingException NoSuchPaddingException
* @throws InvalidAlgorithmParameterException InvalidAlgorithmParameterException
* @throws InvalidKeyException InvalidKeyException
* @throws BadPaddingException BadPaddingException
* @throws IllegalBlockSizeException IllegalBlockSizeException
*/
public static String decryptAES256(String encryptText, String passwordKey, String slat, String stringIv)
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, IOException,
InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
byte[] byteIv = TypeConvert.hexStringToBytes(stringIv);
IvParameterSpec ivParameterSpec = new IvParameterSpec(byteIv);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(passwordKey.toCharArray(), slat.getBytes(), 65536,
256);
SecretKey secretKey = factory.generateSecret(spec);
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
return (new String(cipher.doFinal(Base64.getDecoder().decode(encryptText))));
}
// =================== AES256 Encrypt Method ===================//
/**
* AES256 String 壓縮(加密) (Default Public Key)
*
* @param plainText a String object
* @return encryptAES256 String
* @throws IOException IOException IOException
* @throws NoSuchAlgorithmException NoSuchAlgorithmException
* @throws InvalidKeySpecException InvalidKeySpecException
* @throws NoSuchPaddingException NoSuchPaddingException
* @throws InvalidAlgorithmParameterException InvalidAlgorithmParameterException
* @throws InvalidKeyException InvalidKeyException
* @throws BadPaddingException BadPaddingException
* @throws IllegalBlockSizeException IllegalBlockSizeException
*
*/ public static String encryptAES256(String plainText)
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException,
InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
String slat = getDefaultSlat();
byte[] byteIv = getDefaultByteIv();
String stringIv = TypeConvert.bytesToHexString(byteIv);
String passwordKey = GlobalConstants.ENV_VAR.get(GlobalConstants.AES256_PASSWORD_KEY);
if (StringUtils.isBlank(slat)) {
Map<String, String> mapKeyParis = RSAUtils.loadKeyParisString();
passwordKey = mapKeyParis.get(RSAUtils.PUBLIC_KEY);
} else {
passwordKey = RSAUtils.decryptStringByPrivateKey(slat);
}
return encryptAES256(plainText, passwordKey, slat, stringIv);
}
/**
* AES256 String 壓縮(加密) (Default Public Key)
*
* @param plainText a String object
* @param passwordKey a String object
* @return encryptAES256 String
* @throws IOException IOException IOException
* @throws NoSuchAlgorithmException NoSuchAlgorithmException
* @throws InvalidKeySpecException InvalidKeySpecException
* @throws NoSuchPaddingException NoSuchPaddingException
* @throws InvalidAlgorithmParameterException InvalidAlgorithmParameterException
* @throws InvalidKeyException InvalidKeyException
* @throws BadPaddingException BadPaddingException
* @throws IllegalBlockSizeException IllegalBlockSizeException
*
*/
public static String encryptAES256(String plainText, String passwordKey)
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException,
InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
String slat = getDefaultSlat();
byte[] byteIv = getDefaultByteIv();
String stringIv = TypeConvert.bytesToHexString(byteIv);
return encryptAES256(plainText, passwordKey, slat, stringIv);
}
/**
* AES256 String 壓縮(加密) (Default Public Key)
*
* @param plainText plainText String object
* @param passwordKey passwordKey String object
* @param slat slat String object
* @return encryptAES256 String
*
* @throws IOException IOException IOException
* @throws NoSuchAlgorithmException NoSuchAlgorithmException
* @throws InvalidKeySpecException InvalidKeySpecException
* @throws NoSuchPaddingException NoSuchPaddingException
* @throws InvalidAlgorithmParameterException InvalidAlgorithmParameterException
* @throws InvalidKeyException InvalidKeyException
* @throws BadPaddingException BadPaddingException
* @throws IllegalBlockSizeException IllegalBlockSizeException
*
*/
public static String encryptAES256(String plainText, String passwordKey, String slat)
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException,
InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
byte[] byteIv = getDefaultByteIv();
String stringIv = TypeConvert.bytesToHexString(byteIv);
return encryptAES256(plainText, passwordKey, slat, stringIv);
}
/**
* AES256 String 壓縮(加密) (Default Public Key)
*
* @param plainText plainText String object
* @param passwordKey passwordKey String object
* @param slat slat String object
* @param stringIv stringIv String object
* @return encryptAES256 String
* @throws IOException IOException IOException
* @throws NoSuchAlgorithmException NoSuchAlgorithmException
* @throws InvalidKeySpecException InvalidKeySpecException
* @throws NoSuchPaddingException NoSuchPaddingException
* @throws InvalidAlgorithmParameterException InvalidAlgorithmParameterException
* @throws InvalidKeyException InvalidKeyException
* @throws BadPaddingException BadPaddingException
* @throws IllegalBlockSizeException IllegalBlockSizeException
*
*/
public static String encryptAES256(String plainText, String passwordKey, String slat, String stringIv)
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException,
InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
byte[] byteIv = TypeConvert.hexStringToBytes(stringIv);
IvParameterSpec ivParameterSpec = new IvParameterSpec(byteIv);
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec keySpec = new PBEKeySpec(passwordKey.toCharArray(), slat.getBytes(), 65536,
256);
SecretKey secretKey = secretKeyFactory.generateSecret(keySpec);
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
return (Base64.getEncoder().encodeToString(cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8))));
}
/**
* 產生 AES256 初始化向量 (IV)
*
* @return Byte 初始化向量(IV)
*/
public static byte[] generateAes256ByteIv() {
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
return iv;
}
/**
* 取得預設的 AES256 初始化向量 (IV)
*
* @return Byte 初始化向量(IV)
*/
private static byte[] getDefaultByteIv() {
String stringIv = GlobalConstants.ENV_VAR.get(GlobalConstants.AES256_IV_KEY);
if (StringUtils.isBlank(stringIv)) {
byte[] byteIv = generateAes256ByteIv();
stringIv = TypeConvert.bytesToHexString(byteIv);
GlobalConstants.ENV_VAR.put(GlobalConstants.AES256_IV_KEY, stringIv);
}
return TypeConvert.hexStringToBytes(stringIv);
}
/**
* 取得預設的 AES256 Slat
*
* @return String default SLAT
* @throws IOException
* @throws InvalidKeyException
* @throws NoSuchAlgorithmException
* @throws NoSuchPaddingException
* @throws InvalidKeySpecException
* @throws IllegalBlockSizeException
* @throws BadPaddingException
*/
private static String getDefaultSlat() throws IOException, InvalidKeyException, NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeySpecException, IllegalBlockSizeException, BadPaddingException {
String slat = GlobalConstants.ENV_VAR.get(GlobalConstants.AES256_SALT_KEY);
if (StringUtils.isBlank(slat)) {
Map<String, String> mapKeyParis = RSAUtils.loadKeyParisString();
slat = mapKeyParis.get(RSAUtils.PUBLIC_KEY);
} else {
slat = RSAUtils.decryptStringByPrivateKey(slat);
}
return slat;
}
}
package tw.lewishome.webapp.base.utility.common;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import java.util.Arrays;
public class Aes256UtilsTest {
@Test
public void TestEncryptAES256() {
try {
String encString = Aes256Utils.encryptAES256("planText", "PasswordKey");
System.out.println(encString);
String decString = Aes256Utils.decryptAES256(encString, "PasswordKey");
System.out.println(decString);
// 驗證解密後的字串是否與原始明文相同
assertEquals("planText", decString);
} catch (Exception ex) {
ex.printStackTrace();
}
}
@Test
public void testEncryptAES256WithSaltAndIv() {
try {
String plainText = "testMessage";
String password = "testPassword";
String salt = "testSalt";
byte[] byteIv = Aes256Utils.generateAes256ByteIv();
String stringIv = TypeConvert.bytesToHexString(byteIv);
String encrypted = Aes256Utils.encryptAES256(plainText, password, salt, stringIv);
assertNotNull(encrypted);
String decrypted = Aes256Utils.decryptAES256(encrypted, password, salt, stringIv);
assertEquals(plainText, decrypted);
} catch (Exception e) {
fail("Encryption/decryption should not throw exception: " + e.getMessage());
}
}
@Test
public void testEncryptAES256WithSalt() {
try {
String plainText = "testMessage";
String password = "testPassword";
String salt = "testSalt";
String encrypted = Aes256Utils.encryptAES256(plainText, password, salt);
assertNotNull(encrypted);
String decrypted = Aes256Utils.decryptAES256(encrypted, password, salt);
assertEquals(plainText, decrypted);
} catch (Exception e) {
fail("Encryption/decryption should not throw exception: " + e.getMessage());
}
}
@Test
public void testGenerateAes256ByteIv() {
byte[] iv1 = Aes256Utils.generateAes256ByteIv();
byte[] iv2 = Aes256Utils.generateAes256ByteIv();
assertNotNull(iv1);
assertNotNull(iv2);
assertEquals(16, iv1.length);
assertEquals(16, iv2.length);
assertFalse(Arrays.equals(iv1, iv2));
}
@Test
void testPrivateConstructor() {
assertThrows(IllegalAccessException.class, () -> {
Aes256Utils.class.getDeclaredConstructor().newInstance();
});
}
}