[英]how do i encrypt and decrypt use AES/GCM/ between PHP and JAVA
我用PHP加密用AES/GCM和JAVA通信,但是不行。这是代码。不知道哪里错了?
<?php
$key = "123456789012345678901234567890";
$plaintext = "aaaaaaa";
$encryptStr = aesGcmEncrypt($plaintext, $key);
echo "加密后:" . $encryptStr;
function aesGcmEncrypt($plaintext, $key)
{
$ivlen = openssl_cipher_iv_length($cipher = "aes-128-gcm");
$iv = openssl_random_pseudo_bytes($ivlen);
$ciphertext_raw = openssl_encrypt($plaintext, $cipher, $key, OPENSSL_NO_PADDING, $iv, $tag);
$ciphertext = base64_encode($iv . $ciphertext_raw . $tag);
return $ciphertext;
}
function decrypt($str, $key)
{
$encrypt = base64_decode($str);
$ivlen = openssl_cipher_iv_length($cipher = "aes-128-gcm");
$tag_length = 16;
$iv = substr($encrypt, 0, $ivlen);
$tag = substr($encrypt, -$tag_length);
$ciphertext = substr($encrypt, $ivlen, -$tag_length);
$ciphertext_raw = openssl_decrypt($ciphertext, $cipher, $key, OPENSSL_NO_PADDING, $iv, $tag);
return $ciphertext_raw;
}
这是 java 代码。
private static String aesGcmEncrypt(String content, byte[] key) {
try {
System.out.println(content);
System.out.println(content.getBytes(UTF_8).length);
// 根据指定算法ALGORITHM自成密码器
Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5Padding");
SecretKeySpec skey = new SecretKeySpec(key, "AES");
cipher.init(Cipher.ENCRYPT_MODE, skey);
//获取向量
byte[] ivb = cipher.getIV();
byte[] encodedByteArray = cipher.doFinal(content.getBytes(UTF_8));
byte[] message = new byte[ivb.length + encodedByteArray.length];
System.arraycopy(ivb, 0, message, 0, ivb.length);
System.arraycopy(encodedByteArray, 0, message, ivb.length, encodedByteArray.length);
String ss = Base64.getEncoder().encodeToString(message);
return ss;
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException
| BadPaddingException e) {
return null;
}
}
JAVA代码不能修改,因为不是我的,必须适配java代码。
两种代码的行为可能与您预期的不同。
在 Java 代码中根本没有使用填充,尽管指定了PKCS5Padding
(在 Java 中,PKCS7Padding 的同义词)。 SunJCE Provider 为 GCM 禁用指定的PKCS5Padding
并应用NoPadding
。 这很有用,因为GCM是一种不需要填充的 stream 密码模式。
应该提到的是,该行为取决于版本。 只有早期的 JDK 版本(例如 8、11、12)接受 GCM 的PKCS5Padding
并将其作为NoPadding
运行。 相比之下,更高的 JDK 版本(例如 14、15)会引发异常。 此外,其他提供者的行为可能有所不同。
在 PHP 代码中,密文由openssl_encrypt
作为原始数据返回,因此只有 Base64 编码一次(即在与 IV 和标签连接之后),因为它应该是。 因此,代码的行为就像设置了标志OPENSSL_RAW_DATA
。 这是因为使用了OPENSSL_NO_PADDING
(值为 3),它实际上只是为非对称加密定义的,而不是为对称加密定义的,因此根本不应该在这里应用。 对称加密的标志是OPENSSL_RAW_DATA
(值为 1)和OPENSSL_ZERO_PADDING
(值为 2),因此OPENSSL_NO_PADDING
等价于OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING
OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING
,因此隐式设置OPENSSL_RAW_DATA
。
请注意, OPENSSL_ZERO_PADDING
不启用零填充,但禁用默认的 PKCS7 填充。 与 SunJCE Provider 类似,openssl 也隐式禁用 GCM 的默认 PKCS7 填充,即无论是否设置OPENSSL_ZERO_PADDING
,GCM 都不使用填充。
总之,可以说填充和标志被排除为错误的原因:在两个代码中都没有应用填充,在OPENSSL_RAW_DATA
代码中设置了 OPENSSL_RAW_DATA。
不幸的是,您没有描述错误,所以只能猜测。 问题可能是由不兼容的密钥引起的,因为这两个代码都可以在我的机器上运行并且是兼容的。
正如评论中已经提到的,对于 AES-128/192/256,必须使用 16/24/32 字节的密钥。 在 Java 代码中,密钥的长度决定了 AES 变体,即,例如 16 字节的长度意味着 AES-128。 在 PHP 代码中,必须明确指定 AES 变体,例如aes-128-gcm
。 太长的键被简单地切断,太短的键用 0 值填充。
例如,如果在 Java 代码中使用 16 字节密钥进行加密(AES-128),在aes-128-gcm
代码中使用相同的密钥进行解密,则解密成功。
如果有进一步的问题,请发布使用过的 Java 版本、错误信息以及完整的示例数据,即密钥、明文和密文。
样本数据:
Plaintext (UTF8): The quick brown fox jumps over the lazy dog
Key (UTF8): 0123456789012345
Java 代码提供(在 JDK 11 下)以下密文(由于随机生成的 IV,这对于每个加密当然是不同的):
Ciphertext (Base64): 8DcD/QwKeFG1u2N1ve3mtsX1Lq7js33ESTigT2GH6Lrqrckh5I4qzkJMG3rnuJ9CSFZ1jai8LTChe3tuIJSMLmMTbUQ9mB0=
IV (hex): f03703fd0c0a7851b5bb6375
这个密文可以通过 PHP decrypt
方法使用aes-128-cbc
和上面的密钥来解密:
print(decrypt("8DcD/QwKeFG1u2N1ve3mtsX1Lq7js33ESTigT2GH6Lrqrckh5I4qzkJMG3rnuJ9CSFZ1jai8LTChe3tuIJSMLmMTbUQ9mB0=", "0123456789012345") . "\n");
因此,如果aes-128-cbc
、上述密钥和相同的 IV 被使用, aesGcmEncrypt
返回相同的密文(对于后者,PHP 代码中随机生成的 IV 必须替换为 Java 代码中生成的 IV,当然只有对于这个测试):
$iv = hex2bin('f03703fd0c0a7851b5bb6375'); // IV to use in aesGcmEncrypt
print(aesGcmEncrypt("The quick brown fox jumps over the lazy dog", "0123456789012345") . "\n");
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.