JAVA 如何解密 base64 编码和 RIJNDAEL_256 由 PHP 加密的数据?

How can JAVA decrypt data which was base64 encoded and RIJNDAEL_256 encryped by PHP?

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

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

function encrypt($text, $encryptionKey) {
  return utf8_encode(base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $encryptionKey, $text, MCRYPT_MODE_CBC, "[=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=]")));
}

...并且可以使用这个函数成功解密:

function decrypt($text, $encryptionKey) {
  return utf8_decode(rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $encryptionKey, base64_decode($text), MCRYPT_MODE_CBC, "[=14=][=14=][=14=][=14=][=14=][=14=][=14=][=14=][=14=][=14=][=14=][=14=][=14=][=14=][=14=][=14=][=14=][=14=][=14=][=14=][=14=][=14=][=14=][=14=][=14=][=14=][=14=][=14=][=14=][=14=][=14=][=14=]")));
}

它存储在 MySql-InnoDB 中,collation=latin1_swedish_ci 在 table 中,charset=utf8 在 VARCHAR(255) 列中。


JAVA 中,数据通过使用 Hibernate 的 JPA 存储库检索,然后呈现给此 基于 Rijndael 256 加密和 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 ([=73=][=73=]...)。

我还没有找到任何方法来将模式和 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 亚马逊 Corretto)

问题是由于在发布的代码中使用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 代码加密的数据)。但是请注意,出于安全原因,key/IV 对只能使用一次。因此,如果密钥是固定的,最好使用每次加密时随机生成的 IV。

另外,代码:

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

可以简化为:

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