You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

从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实现的细节:

  1. JCE默认的Blowfish模式是ECB,填充方式为PKCS5Padding(和PKCS7Padding兼容,但明确行为更稳妥)
  2. 你的密钥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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 12:52:02