从JCE迁移到Bouncy Castle(Blowfish)解密出现冗余字符问题
问题
我是Bouncy Castle的新手,需要将基于JCE的Blowfish加解密代码迁移至Bouncy Castle实现,但未找到相关易懂的指引。以下是我的JCE实现类:
import java.security.NoSuchAlgorithmException; import java.security.Security; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import com.sun.crypto.provider.SunJCE; public class JCEBlowfishEncrypterDecrypter { private static final String ALGORITHM = "Blowfish"; public static SecretKeySpec key; public static String crypt(String msg, String k) throws Exception { SecretKeySpec key = init(k); return Hex.byte2hex(intCrypt(msg, key)); } public static String decrypt(String msg, String k) throws Exception { SecretKeySpec key = init(k); return new String(intDecrypt(Hex.hex2byte(msg), key)); } private static byte[] intCrypt(String msg, SecretKeySpec key) throws Exception { Cipher cipher = Cipher.getInstance(ALGORITHM); cipher.init(1, key); return cipher.doFinal(msg.getBytes()); } private static byte[] intDecrypt(byte encrypted[], SecretKeySpec key) throws Exception { Cipher cipher = Cipher.getInstance(ALGORITHM); cipher.init(2, key); return cipher.doFinal(encrypted); } private static SecretKeySpec init(String myKey) throws NoSuchAlgorithmException { SunJCE sunJce = new SunJCE(); Security.addProvider(sunJce); byte raw[] = myKey.getBytes(); return new SecretKeySpec(raw, ALGORITHM); } }
这是我的Bouncy Castle实现类:
import org.bouncycastle.crypto.BufferedBlockCipher; import org.bouncycastle.crypto.engines.BlowfishEngine; import org.bouncycastle.crypto.paddings.PKCS7Padding; import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher; import org.bouncycastle.crypto.params.KeyParameter; public class BouncyCastleBlowfishEncrypterDecrypter { public static String crypt(String msg, String key) throws Exception { PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new BlowfishEngine(), new PKCS7Padding()); cipher.init(true, new KeyParameter(key.getBytes())); byte[] inputAsBytes = msg.getBytes(); byte[] encryptedAsBytes = new byte[cipher.getOutputSize(inputAsBytes.length)]; int numberOfBytesCopiedOnEncryptedAsBytes = cipher.processBytes(inputAsBytes, 0, inputAsBytes.length, encryptedAsBytes, 0); cipher.doFinal(encryptedAsBytes, numberOfBytesCopiedOnEncryptedAsBytes); return Hex.byte2hex(encryptedAsBytes); } public static String decrypt(String msg, String key) throws Exception { PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new BlowfishEngine(), new PKCS7Padding()); cipher.init(false, new KeyParameter(key.getBytes())); byte[] outputConvertedAsBytes = Hex.hex2byte(msg); byte[] decryptedAsBytes = new byte[cipher.getOutputSize(outputConvertedAsBytes.length)]; int numberOfBytesCopiedOnDecryptedAsBytes = cipher.processBytes(outputConvertedAsBytes, 0, outputConvertedAsBytes.length, decryptedAsBytes, 0); cipher.doFinal(decryptedAsBytes, numberOfBytesCopiedOnDecryptedAsBytes); return new String(decryptedAsBytes); } }
测试主方法如下:
public static void main(String[] args) throws Exception { String encrypted = JCEBlowfishEncrypterDecrypter.crypt("Test", "mmTSQOFzSL9xAwXGLMEe1Q=="); String decrypted = JCEBlowfishEncrypterDecrypter.decrypt(encrypted, "mmTSQOFzSL9xAwXGLMEe1Q=="); System.out.println("--------------"); System.out.println("encrypted -> " + encrypted); System.out.println("decrypted -> " + decrypted); encrypted = BouncyCastleBlowfishEncrypterDecrypter.crypt("Test", "mmTSQOFzSL9xAwXGLMEe1Q=="); decrypted = BouncyCastleBlowfishEncrypterDecrypter.decrypt(encrypted, "mmTSQOFzSL9xAwXGLMEe1Q=="); System.out.println("--------------"); System.out.println("encrypted -> " + encrypted); System.out.println("decrypted -> " + decrypted); }
但使用Bouncy Castle解密时出现了奇怪的额外字符,请问问题出在哪里?
问题分析与解决
核心问题
解密后出现额外字符,直接原因是没有正确处理Bouncy Castle解密后的有效字节长度:
cipher.getOutputSize()返回的是理论最大输出长度,实际解密后的有效字节数是processBytes()返回值加上doFinal()返回值的总和- 你直接使用了预先分配的全长度数组,其中包含未被填充的空字节(默认初始化的0值),转成字符串后就会显示为奇怪的额外字符
另外还有两个需要对齐JCE实现的细节:
- JCE默认的Blowfish模式是ECB,填充方式为PKCS5Padding(和PKCS7Padding兼容,但明确行为更稳妥)
- 你的密钥
mmTSQOFzSL9xAwXGLMEe1Q==是Base64编码格式,原JCE代码直接转成字节数组的做法并不正确,应该先解码得到原始密钥字节
修正后的Bouncy Castle实现
import org.bouncycastle.crypto.BufferedBlockCipher; import org.bouncycastle.crypto.engines.BlowfishEngine; import org.bouncycastle.crypto.paddings.PKCS7Padding; import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher; import org.bouncycastle.crypto.params.KeyParameter; import java.util.Base64; public class BouncyCastleBlowfishEncrypterDecrypter { public static String crypt(String msg, String key) throws Exception { // 解码Base64格式的密钥,和JCE行为对齐(原JCE代码的密钥处理方式有误,这里修正) byte[] keyBytes = Base64.getDecoder().decode(key); PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new BlowfishEngine(), new PKCS7Padding()); cipher.init(true, new KeyParameter(keyBytes)); byte[] inputAsBytes = msg.getBytes(); byte[] encryptedAsBytes = new byte[cipher.getOutputSize(inputAsBytes.length)]; int processed = cipher.processBytes(inputAsBytes, 0, inputAsBytes.length, encryptedAsBytes, 0); int finalBytes = cipher.doFinal(encryptedAsBytes, processed); // 截取实际加密后的有效字节,避免多余空字节 byte[] result = new byte[processed + finalBytes]; System.arraycopy(encryptedAsBytes, 0, result, 0, result.length); return Hex.byte2hex(result); } public static String decrypt(String msg, String key) throws Exception { // 解码Base64格式的密钥 byte[] keyBytes = Base64.getDecoder().decode(key); PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new BlowfishEngine(), new PKCS7Padding()); cipher.init(false, new KeyParameter(keyBytes)); byte[] encryptedBytes = Hex.hex2byte(msg); byte[] decryptedAsBytes = new byte[cipher.getOutputSize(encryptedBytes.length)]; int processed = cipher.processBytes(encryptedBytes, 0, encryptedBytes.length, decryptedAsBytes, 0); int finalBytes = cipher.doFinal(decryptedAsBytes, processed); // 关键:只截取真实有效的解密字节,忽略数组中多余的初始化字节 byte[] result = new byte[processed + finalBytes]; System.arraycopy(decryptedAsBytes, 0, result, 0, result.length); return new String(result); } }
额外说明
- JCE的
Cipher.doFinal()会自动返回精准长度的字节数组,而Bouncy Castle的BufferedBlockCipher需要手动计算并截取有效字节,这是两者的核心差异 - 修正密钥的Base64解码后,JCE和Bouncy Castle的加密结果会完全一致,否则两者使用的密钥实际是不同的(原JCE代码的密钥处理存在隐患)
内容的提问来源于stack exchange,提问作者JulesF
相关产品推荐
相关产品推荐

