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

时间:2017-11-30 15:16:11

标签: java php security encryption cryptography

我遇到的情况是,在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

2 个答案:

答案 0 :(得分: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/

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

编辑:

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

答案 1 :(得分: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