AES 加密有几个关键参数必须保持一致,否则加密和解密无法匹配:

- 算法模式: 最常用的是
AES/CBC/PKCS5Padding或AES/GCM/PKCS5Padding,GCM 模式更现代,提供了认证加密功能,推荐使用。 - 密钥: 一个固定长度的字符串,AES 支持 128, 192, 256 位密钥,对应 16, 24, 32 字节的长度。
- 初始化向量: 一个随机的“盐”,长度通常是 16 字节。IV 必须是随机的,并且需要和密文一起传输,但它本身不是秘密。
- 填充方式: PKCS5Padding 或 PKCS7Padding(两者在大多数情况下效果相同)。
下面我们分别以 AES/CBC/PKCS5Padding 和 更推荐的 AES/GCM/PKCS5Padding 为例,展示 PHP 和 Java 的实现。
使用 AES/CBC 模式 (需要手动处理 IV)
这是最经典的模式,实现简单,但需要注意 IV 的处理。
Java (AES/CBC) 实现
在 Java 中,我们使用 javax.crypto 包。
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class AesCbcExample {
// AES 密钥,必须是 16, 24, 或 32 字节长
private static final String SECRET_KEY = "ThisIsASecretKey12345"; // 32 bytes for AES-256
// IV 长度必须与块大小相同,对于 AES 是 16 字节
private static final String INIT_VECTOR = "RandomInitVector"; // 16 bytes
public static String encrypt(String value) {
try {
IvParameterSpec iv = new IvParameterSpec(INIT_VECTOR.getBytes(StandardCharsets.UTF_8));
SecretKeySpec skeySpec = new SecretKeySpec(SECRET_KEY.getBytes(StandardCharsets.UTF_8), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(value.getBytes());
// 将 IV 和密文拼接,然后进行 Base64 编码
// 这样 Java 和 PHP 都能正确解析
return Base64.getEncoder().encodeToString(iv.getIV()) + ":" + Base64.getEncoder().encodeToString(encrypted);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static String decrypt(String encrypted) {
try {
// 分离 IV 和密文
String[] parts = encrypted.split(":");
if (parts.length != 2) {
throw new IllegalArgumentException("Invalid encrypted string format");
}
byte[] iv = Base64.getDecoder().decode(parts[0]);
byte[] cipherText = Base64.getDecoder().decode(parts[1]);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
SecretKeySpec skeySpec = new SecretKeySpec(SECRET_KEY.getBytes(StandardCharsets.UTF_8), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, ivParameterSpec);
byte[] original = cipher.doFinal(cipherText);
return new String(original);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static void main(String[] args) {
String originalString = "Hello, World! This is a secret message.";
System.out.println("Original: " + originalString);
String encryptedString = encrypt(originalString);
System.out.println("Encrypted: " + encryptedString);
String decryptedString = decrypt(encryptedString);
System.out.println("Decrypted: " + decryptedString);
}
}
关键点:

Cipher.getInstance("AES/CBC/PKCS5Padding"): 指定了算法、模式和填充。- IV 处理: 我们将 IV 和密文用 分隔,然后都进行 Base64 编码,这是跨语言兼容的关键,因为 IV 不是秘密,需要传递给解密方。
Base64.getEncoder().encodeToString(): 用于将二进制数据转换为可传输的字符串。
PHP (AES/CBC) 实现
在 PHP 中,我们使用 openssl_encrypt 和 openssl_decrypt 函数。
<?php
class AesCbcExample
{
// 必须与 Java 中的密钥完全一致
private const SECRET_KEY = 'ThisIsASecretKey12345'; // 32 bytes for AES-256
// 必须与 Java 中的 IV 完全一致
private const INIT_VECTOR = 'RandomInitVector'; // 16 bytes
/**
* 加密
* @param string $data
* @return string
*/
public static function encrypt(string $data): string
{
$encrypted = openssl_encrypt(
$data,
'aes-256-cbc', // 算法: AES-256, 模式: CBC, 填充: PKCS7 (PHP默认)
self::SECRET_KEY,
0, // options
self::INIT_VECTOR
);
if ($encrypted === false) {
throw new \RuntimeException("Encryption failed.");
}
// 将 IV 和密文用 : 分隔,都进行 Base64 编码
// 与 Java 的输出格式保持一致
return base64_encode(self::INIT_VECTOR) . ':' . base64_encode($encrypted);
}
/**
* 解密
* @param string $encrypted
* @return string
*/
public static function decrypt(string $encrypted): string
{
// 分离 IV 和密文
$parts = explode(':', $encrypted);
if (count($parts) !== 2) {
throw new \InvalidArgumentException("Invalid encrypted string format.");
}
$iv = base64_decode($parts[0]);
$cipherText = base64_decode($parts[1]);
$decrypted = openssl_decrypt(
$cipherText,
'aes-256-cbc',
self::SECRET_KEY,
0,
$iv
);
if ($decrypted === false) {
throw new \RuntimeException("Decryption failed.");
}
return $decrypted;
}
}
// --- 测试 ---
$originalString = "Hello, World! This is a secret message.";
echo "Original: " . $originalString . PHP_EOL;
$encryptedString = AesCbcExample::encrypt($originalString);
echo "Encrypted: " . $encryptedString . PHP_EOL;
$decryptedString = AesCbcExample::decrypt($encryptedString);
echo "Decrypted: " . $decryptedString . PHP_EOL;
?>
关键点:
openssl_encrypt($data, 'aes-256-cbc', ...):'aes-256-cbc'指定了算法(AES-256)和模式(CBC),PHP 默认使用 PKCS7 填充,与 Java 的 PKCS5Padding 兼容。- IV 处理: 与 Java 一样,我们手动拼接 Base64 编码的 IV 和密文,并用 分隔,确保格式统一。
使用 AES/GCM 模式 (更推荐,自带认证)
GCM (Galois/Counter Mode) 是认证加密模式,它不仅能加密,还能验证数据在传输过程中是否被篡改,它将 IV 和认证标签打包在一起,比 CBC 更安全。
Java (AES/GCM) 实现
import javax.crypto.*;
import javax.crypto.spec.GCMParameterSpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class AesGcmExample {
// AES-256 密钥
private static final String SECRET_KEY = "ThisIsASecretKey12345"; // 32 bytes
// GCM 推荐的 IV 长度是 12 字节
private static final int IV_LENGTH_BYTE = 12;
// GCM 认证标签长度是 16 字节
private static final int TAG_LENGTH_BIT = 128;
public static String encrypt(String value) {
try {
// 生成随机 IV
byte[] iv = new byte[IV_LENGTH_BYTE];
SecureRandom random = new SecureRandom();
random.nextBytes(iv);
SecretKeySpec skeySpec = new SecretKeySpec(SECRET_KEY.getBytes(StandardCharsets.UTF_8), "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); // GCM 通常不使用填充
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(TAG_LENGTH_BIT, iv);
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, gcmParameterSpec);
byte[] encrypted = cipher.doFinal(value.getBytes());
// 将 IV, 密文和认证标签拼接 (cipherText 包含了密文和标签)
// 然后进行 Base64 编码
return Base64.getEncoder().encodeToString(iv) + ":" + Base64.getEncoder().encodeToString(encrypted);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static String decrypt(String encrypted) {
try {
String[] parts = encrypted.split(":");
if (parts.length != 2) {
throw new IllegalArgumentException("Invalid encrypted string format");
}
byte[] iv = Base64.getDecoder().decode(parts[0]);
byte[] cipherTextWithTag = Base64.getDecoder().decode(parts[1]);
SecretKeySpec skeySpec = new SecretKeySpec(SECRET_KEY.getBytes(StandardCharsets.UTF_8), "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(TAG_LENGTH_BIT, iv);
cipher.init(Cipher.DECRYPT_MODE, skeySpec, gcmParameterSpec);
// 如果数据被篡改,cipher.doFinal 会抛出 AEADBadTagException
byte[] original = cipher.doFinal(cipherTextWithTag);
return new String(original);
} catch (AEADBadTagException ex) {
System.err.println("Decryption failed: Data is corrupted or tampered with.");
return null; // 或者抛出异常
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static void main(String[] args) {
String originalString = "Hello, World! This is a secret GCM message.";
System.out.println("Original: " + originalString);
String encryptedString = encrypt(originalString);
System.out.println("Encrypted: " + encryptedString);
String decryptedString = decrypt(encryptedString);
System.out.println("Decrypted: " + decryptedString);
// 测试篡改数据
String tamperedString = encryptedString.substring(0, 20) + "X" . encryptedString.substring(21);
System.out.println("\nTampered String: " . tamperedString);
String decryptedTampered = decrypt(tamperedString);
System.out.println("Decrypted Tampered: " . (decryptedTampered != null ? decryptedTampered : "Decryption failed as expected."));
}
}
关键点:

- 随机 IV: GCM 模式下,每次加密都必须使用一个全新的、随机的 IV,不能像 CBC 那样使用固定的 IV。
GCMParameterSpec: 用于指定 IV 和认证标签的长度。- 认证标签:
cipher.doFinal()的结果包含了密文和 16 字节的认证标签,Java 会自动处理它们。 - 篡改检测: 如果解密时数据被篡改,会抛出
AEADBadTagException,这是 GCM 安全性的重要体现。
PHP (AES/GCM) 实现
PHP 7.1+ 开始支持 AES-GCM。
<?php
class AesGcmExample
{
// 必须与 Java 中的密钥一致
private const SECRET_KEY = 'ThisIsASecretKey12345'; // 32 bytes
// GCM 推荐的 IV 长度是 12 字节
private const IV_LENGTH = 12;
/**
* 加密
* @param string $data
* @return string
* @throws Exception
*/
public static function encrypt(string $data): string
{
// 生成随机 IV
$iv = random_bytes(self::IV_LENGTH);
$tag = ''; // 初始化认证变量
// openssl_encrypt 会将认证标签填充到这个变量中
$encrypted = openssl_encrypt(
$data,
'aes-256-gcm', // 算法: AES-256, 模式: GCM
self::SECRET_KEY,
0, // options
$iv,
$tag // 用于接收认证标签
);
if ($encrypted === false) {
throw new \RuntimeException("Encryption failed.");
}
// 将 IV, 密文和认证标签拼接
// PHP 的 openssl_encrypt 返回的密文不包含标签,需要手动拼接
return base64_encode($iv) . ':' . base64_encode($encrypted) . ':' . base64_encode($tag);
}
/**
* 解密
* @param string $encrypted
* @return string
* @throws Exception
*/
public static function decrypt(string $encrypted): string
{
$parts = explode(':', $encrypted);
if (count($parts) !== 3) {
throw new \InvalidArgumentException("Invalid encrypted string format.");
}
$iv = base64_decode($parts[0]);
$cipherText = base64_decode($parts[1]);
$tag = base64_decode($parts[2]);
$decrypted = openssl_decrypt(
$cipherText,
'aes-256-gcm',
self::SECRET_KEY,
0,
$iv,
$tag // 将认证标签传递给解密函数
);
if ($decrypted === false) {
// 如果认证失败,openssl_decrypt 会返回 false
throw new \RuntimeException("Decryption failed. Data may be corrupted or tampered with.");
}
return $decrypted;
}
}
// --- 测试 ---
$originalString = "Hello, World! This is a secret GCM message.";
echo "Original: " . $originalString . PHP_EOL;
$encryptedString = AesGcmExample::encrypt($originalString);
echo "Encrypted: " . $encryptedString . PHP_EOL;
$decryptedString = AesGcmExample::decrypt($encryptedString);
echo "Decrypted: " . $decryptedString . PHP_EOL;
// 测试篡改数据
$tamperedString = substr($encryptedString, 0, 30) . "X" . substr($encryptedString, 31);
echo "\nTampered String: " . $tamperedString . PHP_EOL;
try {
$decryptedTampered = AesGcmExample::decrypt($tamperedString);
echo "Decrypted Tampered: " . $decryptedTampered . PHP_EOL;
} catch (\RuntimeException $e) {
echo "Decryption Tampered: " . $e->getMessage() . PHP_EOL;
}
?>
关键点:
random_bytes(self::IV_LENGTH): 生成随机的 IV。$tag参数:openssl_encrypt的第六个参数是一个引用变量,用于接收生成的认证标签。- 输出格式: 我们将 IV、密文和认证标签三部分用 分隔并 Base64 编码,这与 Java 的输出格式略有不同(Java 的密文+标签是连在一起的),但逻辑一致。
- 解密: 将接收到的三部分数据分离,并把认证标签
$tag传递给openssl_decrypt,如果标签不匹配,解密会失败并返回false。
总结与最佳实践
| 特性 | AES/CBC | AES/GCM (推荐) |
|---|---|---|
| 安全性 | 良好,但易受填充预言攻击和 IV 重放攻击影响。 | 非常高,提供认证加密,防止篡改。 |
| IV | 必须固定,且需要保密(虽然通常认为不保密也可,但最佳实践是保密)。 | 必须随机,每次加密都不同,无需保密。 |
| 密文结构 | Base64(IV) : Base64(密文) |
Base64(IV) : Base64(密文) : Base64(认证标签) (PHP) 或 Base64(IV) : Base64(密文+标签) (Java) |
| 跨语言 | 实现简单,但需手动处理 IV。 | 实现稍复杂,需正确处理随机 IV 和认证标签。 |
| 错误处理 | 解密失败通常返回乱码或抛出异常。 | 解密失败会明确抛出异常(Java: AEADBadTagException, PHP: openssl_decrypt 返回 false),能检测到篡改。 |
最终建议:
- 优先选择 AES/GCM: 它更安全,功能更强大(自带认证),是现代加密的首选。
- 保持参数一致: 确保 Java 和 PHP 使用的密钥、IV(如果是 CBC)、算法模式完全相同。
- 正确处理 IV:
- CBC: 使用一个预定义的、安全的 IV。
- GCM: 每次加密都生成一个新的随机 IV,并将其与密文一起传给解密方。
- 使用 Base64 编码: 因为 IV、密文和标签都是二进制数据,为了在网络或文本环境中传输,必须进行 Base64 编码。
- 妥善保管密钥: 密钥是安全的核心,切勿硬编码在代码中或提交到版本控制系统,应使用环境变量、密钥管理服务等方式安全地存储和获取密钥。
