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

Bouncy Castle签名后Base64解码出现EOT/ETX乱码问题求助

PKCS#7签名后解码出现特殊字符的问题解决

问题描述

我使用Bouncy Castle库(版本:jdk15on-1.57,Java 8)实现了一个字符串签名函数,返回包含原内容的签名结果Base64值。核心代码如下:

String stringMessage = "exampleContentBlaBla";

// Load the keystore
KeyStore keystore = KeyStore.getInstance("JKS");

keystore.load(new FileInputStream("path/to/keystore.jks"), "keystorePassword".toCharArray());

// Get the private key and certificate
String alias = "yourAlias";
PrivateKey privateKey = (PrivateKey) keystore.getKey(alias, "keyPassword".toCharArray());
Certificate certificate = keystore.getCertificate(alias);

// Initialize CMS components
CMSSignedDataGenerator generator = new CMSSignedDataGenerator();
CMSTypedData content = new CMSProcessableByteArray(stringMessage.getBytes("UTF-8"));

// Create a JcaSimpleSignerInfoGeneratorBuilder and configure the signer
JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder("SHA256withRSA");
ContentSigner contentSigner = contentSignerBuilder.build(privateKey);

JcaSignerInfoGeneratorBuilder builder = new JcaSignerInfoGeneratorBuilder(
        new JcaDigestCalculatorProviderBuilder().setProvider("BC").build());
generator.addSignerInfoGenerator(builder.build(contentSigner, certificate));

// Generate the signed data
CMSSignedData signedData = generator.generate(content, true);

// Encode the signed data to Base64
byte[] pkcs7Bytes = signedData.getEncoded();
String encodedData = new String(Base64.encode(pkcs7Bytes), "UTF-8");

但解码结果时,字符串中出现EOT、ETX及x82等特殊字符,无法解析出原内容,尽管签名验证已通过。解码代码如下:

String testDecoded = new String(Base64.decode(encodedString.getBytes("UTF-8")));
System.out.println(testDecoded );

单独进行Base64编解码无问题,且所有环节已显式指定UTF-8编码,确认特殊字符是签名过程后产生的,求原因及解决办法。

原因分析

问题的核心在于错误地将二进制PKCS#7签名数据当作UTF-8字符串处理:

  1. 签名生成的pkcs7Bytes是DER编码的PKCS#7/CMS二进制格式,包含大量非UTF-8兼容的字节(如x82这类超出ASCII范围且不符合UTF-8编码规则的字节),强行转成UTF-8字符串会破坏原始二进制结构,出现控制字符或乱码。
  2. PKCS#7是封装格式,原内容被打包在二进制结构内部,并非明文拼接在签名之后,直接转字符串无法正确提取原内容。

解决办法

不要直接将Base64解码后的二进制数据转为字符串,而是使用Bouncy Castle的CMS API解析签名数据,提取原始内容:

正确的解码与原内容提取代码

// 1. Base64解码得到PKCS#7二进制数据
byte[] pkcs7Bytes = Base64.decode(encodedString.getBytes("UTF-8"));

// 2. 解析CMS签名数据
CMSSignedData signedData = new CMSSignedData(pkcs7Bytes);

// 3. 提取原始内容
CMSProcessableByteArray content = (CMSProcessableByteArray) signedData.getSignedContent();
byte[] originalContentBytes = (byte[]) content.getContent();

// 4. 转换为原始字符串(原内容为UTF-8编码)
String originalContent = new String(originalContentBytes, "UTF-8");
System.out.println(originalContent); // 输出exampleContentBlaBla

额外注意事项

  • 禁止将加密/签名类二进制数据直接转为字符串(Base64等文本编码除外),二进制数据中的非文本字节会导致字符串转码损坏。
  • 签名验证需通过Bouncy Castle的API完成,示例代码如下:
Store<?> certStore = signedData.getCertificates();
SignerInformationStore signerInfos = signedData.getSignerInfos();
for (SignerInformation signerInfo : signerInfos.getSigners()) {
    Collection<?> certCollection = certStore.getMatches(signerInfo.getSID());
    X509Certificate cert = (X509Certificate) certCollection.iterator().next();
    if (signerInfo.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert))) {
        System.out.println("签名验证通过");
    }
}

内容的提问来源于stack exchange,提问作者Aleks

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 07:55:04