
在现代应用开发中,不同编程语言之间的数据安全交换是常见的需求。aes/gcm(advanced encryption standard / galois/counter mode)是一种推荐的认证加密模式,它不仅提供数据保密性,还提供数据完整性和认证性。然而,在php和java等不同平台间实现aes/gcm的互操作性,需要严格遵循相同的加密参数、密钥处理、iv(initialization vector)生成、认证标签(authentication tag)长度以及数据编码/解码规范。任何细微的不一致都可能导致解密失败,通常表现为java端的aeadbadtagexception。
首先,我们来分析PHP端的加密实现,这是理解Java端如何正确解密的基础。PHP的aes_gcm_encrypt函数定义了加密过程:
<?php
function aes_gcm_encrypt($data, $secret) {
$cipher = 'aes-128-gcm'; // 算法:AES-128-GCM
$string = is_array($data) ? json_encode($data) : $data;
$skey = hex2bin($secret); // 密钥:将十六进制字符串转换为二进制
// IV:使用openssl_random_pseudo_bytes生成随机IV
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($cipher));
$tag = NULL; // 认证标签:由openssl_encrypt生成
// 加密:OPENSSL_RAW_DATA表示输出原始二进制密文
$content = openssl_encrypt($string, $cipher, $skey, OPENSSL_RAW_DATA, $iv, $tag);
// 输出格式:将IV、密文和Tag的二进制数据转换为十六进制字符串,然后拼接
$str = bin2hex($iv) . bin2hex($content) . bin2hex($tag);
// 最后将拼接后的十六进制字符串转换为二进制,再进行Base64编码
return base64_encode(hex2bin($str));
}
// 示例调用
$content = 'Test text.{123456}';
$secret = '544553544B4559313233343536'; // 十六进制密钥
$encryptStr = aes_gcm_encrypt($content, $secret);
print_r("encrypt -> $encryptStr \n");
?>关键点总结:
PHP的解密函数aes_gcm_decrypt也印证了这一编码方式:
<?php
function aes_gcm_decrypt($content, $secret) {
$cipher = 'aes-128-gcm';
// 1. Base64解码得到二进制数据
// 2. 将二进制数据再转换为十六进制字符串(与加密时的hex2bin($str)反向操作)
$ciphertextwithiv = bin2hex(base64_decode($content));
// 从十六进制字符串中提取IV、Tag和密文
$iv = substr($ciphertextwithiv, 0, 24); // 24个十六进制字符 = 12字节IV
$tag = substr($ciphertextwithiv , -32, 32); // 32个十六进制字符 = 16字节Tag
$ciphertext = substr($ciphertextwithiv, 24, strlen($ciphertextwithiv) - 24 - 32);
$skey = hex2bin($secret);
// 解密时,需要将提取出的十六进制IV、Tag和密文转换回二进制
return openssl_decrypt(hex2bin($ciphertext), $cipher, $skey, OPENSSL_RAW_DATA, hex2bin($iv), hex2bin($tag));
}
?>从PHP解密逻辑中我们可以明确:
立即学习“PHP免费学习笔记(深入)”;
最初的Java解密尝试遭遇了AEADBadTagException,这通常意味着密钥、IV、认证标签或密文在解密时存在不匹配。
import java.security.spec.KeySpec;
import java.util.Base64;
import java.util.Random; // 注意:这里使用了Random生成salt
import javax.crypto.*;
import javax.crypto.spec.*;
public class MyTest {
// ... main 方法 ...
private static String decrypt(String data, String mainKey, int ivLength) throws Exception {
final byte[] encryptedBytes = Base64.getDecoder().decode(data.getBytes("UTF8"));
final byte[] initializationVector = new byte[ivLength]; // IV长度问题
System.arraycopy(encryptedBytes, 0, initializationVector, 0, ivLength);
// 密钥生成方式与PHP不一致
SecretKeySpec secretKeySpec = new SecretKeySpec(generateSecretKeyFromPassword(mainKey, mainKey.length()), "AES");
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, initializationVector);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, gcmParameterSpec);
// 密文和Tag的提取方式
return new String(cipher.doFinal(encryptedBytes, ivLength, encryptedBytes.length - ivLength), "UTF8");
}
// 错误的密钥生成方法
private static byte[] generateSecretKeyFromPassword(String password, int keyLength) throws Exception {
byte[] salt = new byte[keyLength];
new Random(password.hashCode()).nextBytes(salt);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 128);
return factory.generateSecret(spec).getEncoded();
}
}问题分析:
为了与PHP的加密行为完全兼容,Java端的解密代码需要进行以下修正:
以下是修正后的Java解密代码:
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import javax.crypto.*;
import javax.crypto.spec.*;
public class MyTest {
public static final String ALGO = "AES"; // 算法名称
public static final String GCM_ALGO = "AES/GCM/NoPadding"; // GCM模式,不填充
public static final int IV_LENGTH = 12; // IV长度:12字节 (96位)
public static void main(String[] args) throws Exception {
String secret = "544553544B4559313233343536"; // PHP提供的十六进制密钥
String encryptStr = "Fun3yZTPcHsxBpft+jBZDe2NjGNAs8xUHY21eZswZE4iLKYdBsyER7RwVfFvuQ=="; // PHP加密结果
// 格式化密钥,确保其长度为32个十六进制字符(16字节)
secret = reformatSecret(secret);
String decryptStr = decrypt(encryptStr, secret);
System.out.println("encryptString: " + encryptStr);
System.out.println("secret: " + secret);
System.out.println("decryptString: " + decryptStr);
}
/**
* 解密PHP加密的数据
* @param data Base64编码的加密字符串
* @param secret 十六进制格式的密钥
* @return 解密后的原始字符串
* @throws Exception
*/
private static String decrypt(String data, String secret) throws Exception {
// 1. Base64解码加密数据
final byte[] encryptedBytes = Base64.getDecoder().decode(data.getBytes(StandardCharsets.UTF_8));
// 2. 提取IV
final byte[] initializationVector = new byte[IV_LENGTH];
System.arraycopy(encryptedBytes, 0, initializationVector, 0, IV_LENGTH);
// 3. 将十六进制密钥字符串转换为字节数组
final byte[] key = parseHexStr2Byte(secret);
// 4. 创建SecretKeySpec
SecretKeySpec secretKeySpec = new SecretKeySpec(key, ALGO);
// 5. 创建GCMParameterSpec,GCM模式默认Tag长度为128位(16字节)
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, initializationVector);
// 6. 初始化Cipher为解密模式
Cipher cipher = Cipher.getInstance(GCM_ALGO);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, gcmParameterSpec);
// 7. 执行解密:从IV之后开始,直到数据末尾,包含密文和Tag
return new String(cipher.doFinal(encryptedBytes, IV_LENGTH, encryptedBytes.length - IV_LENGTH), StandardCharsets.UTF_8);
}
/**
* 格式化密钥字符串,使其长度为32个十六进制字符(对应16字节)
* 如果不足32位,则补零;如果超过32位,则截断。
* @param secret 原始十六进制密钥字符串
* @return 格式化后的32位十六进制密钥字符串
*/
public static String reformatSecret(String secret) {
if (secret == null || secret.length() < 1) {
return "";
}
int secretLen = secret.length();
if (secretLen < 32) {
StringBuilder str = new StringBuilder(secret);
while (secretLen < 32) {
str.append("0"); // 补零
secretLen = str.length();
}
return str.toString();
} else {
return secret.substring(0, 32); // 截断
}
}
/**
* 将十六进制字符串转换为字节数组
* @param hexStr 十六进制字符串
* @return 对应的字节数组
*/
public static byte[] parseHexStr2Byte(String hexStr) {
int len = hexStr.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(hexStr.charAt(i), 16) << 4) + Character.digit(hexStr.charAt(i+1), 16));
}
return data;
}
}以上就是跨语言AES/GCM/128加解密:PHP与Java互操作指南的详细内容,更多请关注php中文网其它相关文章!
PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号