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字符串处理:
- 签名生成的
pkcs7Bytes是DER编码的PKCS#7/CMS二进制格式,包含大量非UTF-8兼容的字节(如x82这类超出ASCII范围且不符合UTF-8编码规则的字节),强行转成UTF-8字符串会破坏原始二进制结构,出现控制字符或乱码。 - 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
相关产品推荐
相关产品推荐

