JAVA如何解密用PHP加密的base64编码和RIJNDAEL 256的数据?

Posted

技术标签:

【中文标题】JAVA如何解密用PHP加密的base64编码和RIJNDAEL 256的数据?【英文标题】:How can JAVA decrypt data which was base64 encoded and RIJNDAEL_256 encryped by PHP? 【发布时间】:2020-08-19 06:31:26 【问题描述】:

我需要将数据从 php 迁移到 JAVA 应用程序。

数据已使用此 PHP 函数加密:

function encrypt($text, $encryptionKey) 
  return utf8_encode(base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $encryptionKey, $text, MCRYPT_MODE_CBC, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0")));

...使用此函数可以成功解密:

function decrypt($text, $encryptionKey) 
  return utf8_decode(rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $encryptionKey, base64_decode($text), MCRYPT_MODE_CBC, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0")));

它存储在 mysql-InnoDB 中,collation=latin1_swedish_ci 在一个表中,charset=utf8 在 VARCHAR(255) 列中。


JAVA 中,数据通过使用 Hibernate 的 JPA 存储库检索,然后呈现给此 基于Rijndael 256 encryption with Java & Bouncy Castle 的方法:

protected String decryptRijndael256_(String encryptedBase64Text) 
  byte[] encryptedBytes = Base64.getDecoder().decode(encryptedBase64Text.getBytes(StandardCharsets.UTF_8));
  byte[] key = encryptionKey.getBytes(StandardCharsets.UTF_8);

  RijndaelEngine rijndaelEngine = new RijndaelEngine(256);
  KeyParameter keyParam = new KeyParameter(key);
  rijndaelEngine.init(false, keyParam);
  PaddedBufferedBlockCipher bufferedBlock = new PaddedBufferedBlockCipher(rijndaelEngine, new ZeroBytePadding());

  byte[] decryptedBytes = new byte[bufferedBlock.getOutputSize(encryptedBytes.length)];
  int processed = bufferedBlock.processBytes(encryptedBytes, 0, encryptedBytes.length, decryptedBytes, 0);
  processed += bufferedBlock.doFinal(decryptedBytes, processed);
  decryptedBytes = Arrays.copyOfRange(decryptedBytes, 0, processed);

  return new String(decryptedBytes, StandardCharsets.UTF_8);

虽然这在加密文本如下所示的大多数情况下都有效:

5VTv/x2f41Aj2iES7B9lRUi8Q9gH3MYnSR3xc4X1di4= => account.name@gmail.com

...如果文本很长并且看起来像这样,则会失败:

p77KGdWlexQXLGPZzkAqk2OK6oC9r7TDfMfaDhofu0et7RaPcA0hUCq0mBnY4oakjZpIrBeMadwhYonVKwJlGw== => very.long.account.name@gmail.c���ե.c��@*�c�ꀽ���|���G


所以它大部分被正确解码,但最后一个块似乎是问题所在。我怀疑问题出在字符编码或MCRYPT_MODE_CBC 及其IV (\0\0...) 上。

我还没有找到任何方法将模式和 IV 添加到 java 实现中。

关于 Base64 加密。我从java.util.Base64org.apache.commons.codec.binary.Base64org.bouncycastle.util.encoders.Base64 尝试了任何可能的方法。

结果要么如上,要么:

org.bouncycastle.crypto.DataLengthException: last block incomplete in decryption
    at org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher.doFinal(Unknown Source)

我的猜测是问题与 PHP 如何进行填充有关。


问题:

PHP 和 JAVA 处理解密的方式有何不同?如何正确解码所有值?


(PHP:5.6.40,JAVA:11.0.7_10 Amazon Corretto)

【问题讨论】:

仅供参考:MCrypt 扩展已被弃用了 很长 时间,并在 PHP7 中被删除。此外,Rijndael 密码有时几乎是 AES,但实际上并非如此,如果您将数据迁移到实际的 AES,您的生活会更轻松。 @Sammitch 以及它的遗留代码,因此我们正在迁移 - 使用一种我有点过敏的语言 - 但是现在看起来我可能还需要使用一些 PHP 进行迁移,除非我得到答案在这里 【参考方案1】:

该问题是由于在发布的代码中使用 ECB 模式而不是 CBC 模式引起的。应用 CBC 模式替换:

RijndaelEngine rijndaelEngine = new RijndaelEngine(256);
KeyParameter keyParam = new KeyParameter(key);
rijndaelEngine.init(false, keyParam);
PaddedBufferedBlockCipher bufferedBlock = new PaddedBufferedBlockCipher(rijndaelEngine, new ZeroBytePadding());

与:

byte[] iv = new byte[32]; // 0-IV, analogous to PHP code
PaddedBufferedBlockCipher bufferedBlock = new PaddedBufferedBlockCipher(new CBCBlockCipher(new RijndaelEngine(256)), new ZeroBytePadding());
CipherParameters keyAndIV = new ParametersWithIV(new KeyParameter(key), iv);
bufferedBlock.init(false, keyAndIV);

使用发布代码中的keyiv 是一个包含 IV 的 byte[],其大小对应于块大小(32 字节)。类似于 PHP 代码,必须使用 0-IV(解密使用 PHP 代码加密的数据)。但是请注意,出于安全原因,密钥/IV 对只能使用一次。因此,如果密钥是固定的,最好使用一个 IV,它是为每次加密随机生成的。

另外,代码:

decryptedBytes = Arrays.copyOfRange(decryptedBytes, 0, processed);
return new String(decryptedBytes, StandardCharsets.UTF_8);

可以简化为:

return new String(decryptedBytes, 0, processed, StandardCharsets.UTF_8);

【讨论】:

你是救命稻草,我一直在查看 BlockCipherPadding 的实现,但不知何故错过了查看另一个接口 BlockCipher 及其实现 我似乎并不清楚这一点,我需要对数据进行解码,以便在新应用程序中以更好的方式对其进行加密,而不仅仅是按原样阅读,但你的笔记是非常感谢 - IV 有点像我理解的散列时应该使用的盐,所以应该以类似的方式处理 @Holly - 关于新实现:除了IV 应遵循的规则外,还应彻底重新考虑算法、模式和填充,例如AES-GCM 和Pkcs7-Padding 是更可靠的替代方案。 澄清:AES-GCM Pkcs7-Padding 在某种程度上具有误导性,因为 GCM 不使用填充。我的真正意思是:对于需要填充的模式(例如像 CBC),Pkcs7 比零填充更可靠。

以上是关于JAVA如何解密用PHP加密的base64编码和RIJNDAEL 256的数据?的主要内容,如果未能解决你的问题,请参考以下文章

Java Base64 加密/解密

用C#实现Base64处理,加密解密,编码解码

Java基础加密之BASE64加解密

为啥我用Base64加密后,不能将它解密?

Java加密与解密

如何使用Base64进行加密和解密