【问题标题】:AES-256-CBC encrypted with PHP and decrypt in JavaAES-256-CBC 用 PHP 加密并用 Java 解密
【发布时间】:2017-11-30 15:16:11
【问题描述】:

我的情况是 JSON 在 PHP 的 openssl_encrypt 中加密,需要在 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());

上面的 sn-p 似乎正在使用 openssl_encrypt

编辑#2

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

PHP 加密,JAVA 解密使用AES/CBC/NoPadding

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

【问题讨论】:

  • 使用长度正确的密钥,“aes-256”是一个 32 字节的密钥。
  • 为什么要使用 370 字节的 AES 密钥?
  • ^ 因为密钥生成不在我手中。它在 PHP 端完成并在那里完美运行。

标签: java php security encryption cryptography


【解决方案1】:

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

编码:

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

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

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

简单地将 data 和 iv 视为 byte[]

根据 openssl 生成密钥

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

OpenSSL EVP_BytesToKey issue in Java

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

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

编辑:

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

【讨论】:

  • 我同意第一部分。简单地说,现代密码对字节(或八位字节字符串)而不是字符串进行操作。对问题中的文本字符串所做的任何操作都是错误,尤其是在 Java 中。在 Java 中,您需要对 byte[] 进行操作。请注意,PHP 可以使用转义对字符串中的任何内容进行编码,因此那里的密文可能是正确的...如果我查看openssl_encrypt API 描述,第二部分似乎不正确- 我只看到密钥和 IV,没有密码和盐。
  • 我已将实现更改为byte[]。但是,是的,你们是对的,PHP 以某种方式将超出的长度截断为 256 位。我做了一些改变,现在正在工作
  • 请检查更新。我认为如果默认情况下在openssl_en/decrypt 中截断密钥,这会产生某种误导。这可能是一个愚蠢的问题,但是如果 2 个键具有相同的初始 256 位怎么办?
【解决方案2】:

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

注意事项:
-默认算法是 AES-128-CBC。
- 默认初始化向量为 16 个字节。
-编码结果 = base64(initVector + aes crypt)。
- 编码/解码结果以自身对象的形式呈现,它变得更有帮助,并有可能在编码/解码操作后检查错误、获取错误消息并获取初始向量值。

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

【讨论】:

  • 你应该添加简短的解释,然后链接到完整的解决方案
猜你喜欢
  • 1970-01-01
  • 2013-08-11
  • 2017-09-28
  • 2014-02-06
  • 2019-11-17
  • 1970-01-01
  • 2017-07-12
  • 2022-11-16
  • 2021-06-07
相关资源
最近更新 更多