采用encrypt->sign->encrypt流程封装数据时CMSException异常求助
解决DER长度错误:encrypt->sign->encrypt流程中的BouncyCastle CMS封装问题
你遇到的DER length more than 4 bytes: 98错误,根源在于重复使用了同一个CMSEnvelopedDataStreamGenerator实例来创建内层和外层的EnvelopedData。BouncyCastle的CMS流生成器是有状态的,不能被复用在多个加密操作中——复用会破坏内部的ASN.1编码状态,最终导致生成的内容结构混乱,触发解析时的长度校验失败。
问题代码分析
在你的加密流程中,你只初始化了一次envelopedGenerator,却用它同时创建了内层和外层的加密流:
envelopedGenerator = getCMSEnvelopedDataStreamGenerator(builder); // 用同一个生成器创建内层加密流 OutputStream envelopedStream1 = envelopedGenerator.open(signingStream,settings.getEncryptor()); // 又用同一个生成器创建外层加密流 OutputStream envelopedStream2 = envelopedGenerator.open(CMSObjectIdentifiers.signedData,out,settings.getEncryptor());
这种复用会让生成器的编码状态混乱,最终生成的外层EnvelopedData不符合DER规范,解密后得到的SignedData内容存在编码错误,进而触发解析时的IOException。
修复方案
你需要为每一层加密操作创建独立的CMSEnvelopedDataStreamGenerator实例,确保每一层的加密都是状态干净、独立的。同时严格遵循encrypt→sign→encrypt的数据流顺序:
修改后的加密代码如下:
CMSSignedDataStreamGenerator signedGenerator; try { signedGenerator = getCMSSignedDataStreamGenerator(builder); // 为内层加密创建独立生成器 CMSEnvelopedDataStreamGenerator innerEnvelopedGenerator = getCMSEnvelopedDataStreamGenerator(builder); // 为外层加密创建独立生成器 CMSEnvelopedDataStreamGenerator outerEnvelopedGenerator = getCMSEnvelopedDataStreamGenerator(builder); // 外层加密流:最终输出到out,内容类型为signedData OutputStream envelopedStream2 = outerEnvelopedGenerator.open(CMSObjectIdentifiers.signedData, out, settings.getEncryptor()); // 签名流:内容类型为envelopedData,输出到外层加密流 OutputStream signingStream = signedGenerator.open(CMSObjectIdentifiers.envelopedData, envelopedStream2, encapsulate); // 内层加密流:输出到签名流,加密原始内容 OutputStream envelopedStream1 = innerEnvelopedGenerator.open(signingStream, settings.getEncryptor()); // 按从内到外的顺序写入内容并关闭流,确保ASN.1结构完整编码 content.writeTo(envelopedStream1); envelopedStream1.close(); signingStream.close(); envelopedStream2.close(); } catch (CMSException | MessagingException e) { throw new IOException(e.toString()); }
额外注意事项
- 流关闭顺序:必须从内到外关闭流(内层加密流→签名流→外层加密流),确保每一层的ASN.1结构都能正确完成编码。
- 生成器独立性:不仅是
CMSEnvelopedDataStreamGenerator,CMSSignedDataStreamGenerator也不建议复用,每次签名操作都应创建新实例,避免状态污染。 - 解密流程补全:你的测试代码中,解密外层EnvelopedData后解析
CMSSignedData是正确的,但验证签名后,还需要从SignedData中提取内层EnvelopedData并再次解密,才能获取原始内容(如果需要的话)。
内容的提问来源于stack exchange,提问作者Maria Sigal
相关产品推荐
相关产品推荐

