AES-256-CBC用PHP加密并用Java解密

问题描述 投票:0回答:2

我在PHP的openssl_encrypt中加密JSON并需要在JAVA中解密。

$encrypted = "...ENCRYPTED DATA...";
$secretFile = "/path/to/secret/saved/in/text_file";
$secret = base64_decode(file_get_contents($secretFile));
var_dump(strlen($secret)); // prints : int(370)

$iv = substr($encrypted, 0, 16);
$data = substr($encrypted, 16);
$decrypted = openssl_decrypt($data, "aes-256-cbc", $secret, null, $iv);

这个$decrypted有正确的数据,现在已被解密。

现在,问题是当我尝试用Java做同样的事情时,它不起作用:(

String path = "/path/to/secret/saved/in/text";
String payload = "...ENCRYPTED DATA...";
StringBuilder output = new StringBuilder();

String iv = payload.substring(0, 16);
byte[] secret = Base64.getDecoder().decode(Files.readAllBytes(Paths.get(path)));
String data = payload.substring(16);

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec secretKeySpec = new SecretKeySpec(secret, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv.getBytes(), 0, cipher.getBlockSize());
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec); // This line throws exception : 

cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));

这里是:

Exception in thread "main" java.security.InvalidKeyException: Invalid AES key length: 370 bytes
at com.sun.crypto.provider.AESCrypt.init(AESCrypt.java:87)
at com.sun.crypto.provider.CipherBlockChaining.init(CipherBlockChaining.java:91)
at com.sun.crypto.provider.CipherCore.init(CipherCore.java:591)
at com.sun.crypto.provider.AESCipher.engineInit(AESCipher.java:346)
at javax.crypto.Cipher.init(Cipher.java:1394)
at javax.crypto.Cipher.init(Cipher.java:1327)
at com.sample.App.main(App.java:70)

我已经访问过类似的问题了

AES-256 CBC encrypt in php and decrypt in Java or vice-versa

openssl_encrypt 256 CBC raw_data in java

Unable to exchange data encrypted with AES-256 between Java and PHP

并列表继续......但没有运气

顺便说一下,这就是PHP中加密的方式

$secretFile = "/path/to/secret/saved/in/text_file";
$secret = base64_decode(file_get_contents($secretFile));
$iv = bin2hex(openssl_random_pseudo_bytes(8));
$enc = openssl_encrypt($plainText, "aes-256-cbc", $secret, false, $iv);
return $iv.$enc;

是的,我忘了提到我的JRE已经在UnlimitedJCEPolicy而我无法更改PHP代码。

我完全陷入困境,不能前进。请帮忙。

编辑#1

byte[] payload = ....;
byte[] iv = ....;
byte[] secret = ....; // Now 370 bits
byte[] data = Base64.getDecoder().decode(payload);

Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec secretKeySpec = new SecretKeySpec(Arrays.copyOfRange(secret, 0, 32), "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv, 0, cipher.getBlockSize());

cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] output = cipher.doFinal(data);

System.out.println(new String(output).trim());

以上片段似乎与openssl_encrypt合作

编辑#2

我不确定这是否正确,但以下是我所做的,双方的加密解密工作正常。

在PHP中加密,JAVA中的Decrypt使用AES/CBC/NoPadding

在JAVA中加密,在PHP中解密使用AES/CBC/PKCS5Padding

java php security encryption cryptography
2个回答
1
投票

我不会提供完整的解决方案,但您应该注意一些差异

编码方式:

String iv = payload.substring(0, 16);
String data = payload.substring(16);

你确定Java和PHP中的IV和数据是一样的(IV是字符串吗?)?如果数据是加密的,则应将它们视为字节数组,而不是字符串。真的确保它们是相同的(在php和java中打印hex / base64)

对于IV,你最后调用iv.getBytes(),但语言环境编码可能会/会破坏你的值。只有当字符串真正是字符串(文本)时才应该使用String。不要将字符串用于二进制文件。

简单地将数据和iv视为byte []

根据openssl的密钥生成

对于使用的aes-256-cbc,AES密钥的长度必须为256位。问题是 - 默认情况下openssl不使用提供的秘密作为密钥(我相信它可以,但我不知道如何在PHP中指定)。

OpenSSL EVP_BytesToKey issue in Java

这是EVP_BytesToKey实现:https://olabini.com/blog/tag/evp_bytestokey/

您应该使用EVP_BytesToKey函数生成256位密钥(它是openssl使用的密钥派生函数)。

编辑:

Maarten(在评论中)是对的。关键参数是关键。似乎PHP函数正在接受任何长度误导的参数。根据一些文章(例如http://thefsb.tumblr.com/post/110749271235/using-opensslendecrypt-in-php-instead-of),密钥被断言或填充到必要的长度(因此看起来370位密钥被截断为256位的长度)。


0
投票

根据您的示例,我编写了完整的PHP和Java代码: AesCipher类:https://gist.github.com/demisang/716250080d77a7f65e66f4e813e5a636

笔记: - 默认算法是AES-128-CBC。 - 默认的初始向量是16个字节。 -Encoded result = base64(initVector + aes crypt)。 -Encoded / Decoded结果作为自身对象出现,它更有帮助,并且可以检查错误,获取错误消息并在编码/解码操作后获取初始向量值。

PHP:

$secretKey = '26kozQaKwRuNJ24t';
$text = 'Some text'
$encrypted = AesCipher::encrypt($secretKey, $text);
$decrypted = AesCipher::decrypt($secretKey, $encrypted);

$encrypted->hasError(); // TRUE if operation failed, FALSE otherwise
$encrypted->getData(); // Encoded/Decoded result
$encrypted->getInitVector(); // Get used (random if encode) init vector
// $decrypted->* has identical methods

JAVA:

String secretKey = "26kozQaKwRuNJ24t";
String text = "Some text";

AesCipher encrypted = AesCipher.encrypt(secretKey, text);
AesCipher decrypted = AesCipher.decrypt(secretKey, encrypted);

encrypted.hasError(); // TRUE if operation failed, FALSE otherwise
encrypted.getData(); // Encoded/Decoded result
encrypted.getInitVector(); // Get used (random if encode) init vector
// decrypted.* has identical methods
© www.soinside.com 2019 - 2024. All rights reserved.