Java8实现Rijndael-128/CBC加密144位密钥报错解决方案
问题结论
PHP 老旧的mcrypt扩展确实支持144位(18字节)密钥执行Rijndael-128/CBC加密,但这个行为不符合Rijndael/AES官方标准,属于mcrypt自身的宽松兼容逻辑,这也是Java侧无论用JCA还是BouncyCastle都报密钥长度错误的核心原因。
原因说明
- 你调用的
rijndael-128是指块大小固定为128位的Rijndael算法,也就是标准AES的算法原型,所有规范实现都仅支持16字节(128位)、24字节(192位)、32字节(256位)三种合法密钥长度。 mcrypt作为已经废弃多年的旧扩展,对密钥长度的校验极其宽松:当传入非标准长度密钥时,不会抛出错误,而是自动在密钥末尾填充0x00空字节,将密钥补齐到大于传入长度的最近合法密钥长度后再执行加密。你传入的18字节密钥,会被自动补6个空字节,凑成24字节(192位)的合法密钥使用。- JCA、BouncyCastle都是严格遵循算法规范实现的,会直接拒绝非标准长度密钥,因此抛出长度不合法的报错。
- 你提供的Java代码还有一处逻辑错误:PHP代码是手动实现PKCS5填充后再传入mcrypt,mcrypt本身不会自动添加填充,但Java代码错误使用了
ZeroBytePadding,会导致填充规则不匹配,即使密钥长度正确也无法得到和PHP一致的结果。
Java侧兼容实现步骤
- 对原始18字节密钥做补零处理,末尾补6个
0x00凑成24字节(192位),作为实际加密使用的密钥 - 移除Cipher配置中的
ZeroBytePadding,不需要引擎额外做填充,对齐PHP手动填充的逻辑 - 所有字符串转字节数组时明确指定字符编码(推荐UTF-8),填充长度计算按字节数而非字符数,对齐PHP
strlen按字节计数的逻辑
修正后的Java代码
import org.bouncycastle.crypto.BufferedBlockCipher; import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.engines.RijndaelEngine; import org.bouncycastle.crypto.modes.CBCBlockCipher; import org.bouncycastle.crypto.params.KeyParameter; import org.bouncycastle.crypto.params.ParametersWithIV; import org.bouncycastle.util.encoders.Hex; import java.nio.charset.StandardCharsets; public class PhpRijndaelCompat { public static void main(String[] args) throws Exception { String plainText = "Hello World"; String key = "***"; // 原始18字符密钥 String iv = "***"; // 16字符IV System.out.println(encrypt(plainText, key, iv)); } private static String encrypt(String plainText, String key, String iv) throws Exception { // 对齐PHP mcrypt的密钥补零逻辑:18字节补到24字节(192位) byte[] originalKeyBytes = key.getBytes(StandardCharsets.UTF_8); byte[] realKeyBytes = new byte[24]; System.arraycopy(originalKeyBytes, 0, realKeyBytes, 0, originalKeyBytes.length); byte[] ivBytes = iv.getBytes(StandardCharsets.UTF_8); byte[] paddedPlainBytes = pkcs5Pad(plainText).getBytes(StandardCharsets.UTF_8); // 不添加额外填充,对齐PHP手动填充逻辑 BufferedBlockCipher cipher = new BufferedBlockCipher(new CBCBlockCipher(new RijndaelEngine(128))); CipherParameters params = new ParametersWithIV(new KeyParameter(realKeyBytes), ivBytes); cipher.init(true, params); byte[] outBuf = new byte[cipher.getOutputSize(paddedPlainBytes.length)]; int processLen = cipher.processBytes(paddedPlainBytes, 0, paddedPlainBytes.length, outBuf, 0); int finalLen = cipher.doFinal(outBuf, processLen); byte[] encrypted = new byte[processLen + finalLen]; System.arraycopy(outBuf, 0, encrypted, 0, encrypted.length); return Hex.toHexString(encrypted); } // 完全对齐PHP的pkcs5_pad逻辑,按字节计算填充长度 private static String pkcs5Pad(String text) { int blockSize = 16; int byteLen = text.getBytes(StandardCharsets.UTF_8).length; int padLen = blockSize - (byteLen % blockSize); return text.concat(String.valueOf((char) padLen).repeat(padLen)); } }
通用补零规则参考:如果后续遇到其他非标准长度密钥,按如下规则补零即可和mcrypt行为对齐:
- 密钥长度≤16字节:补零到16字节(128位)
- 16字节<密钥长度≤24字节:补零到24字节(192位)
- 24字节<密钥长度≤32字节:补零到32字节(256位)
内容的提问来源于stack exchange,提问作者Prakhar
相关产品推荐
相关产品推荐

