基于BouncyCastle:如何从分离式签名文件获取附加文件
获取分离式CMSSignedData的完整附加内容
问题背景
持有仅包含签名信息的分离式CMSSignedData实例,需将其与对应原始内容结合,得到包含完整附加内容的实例。
已实现的原始内容获取逻辑
通过签名属性中存储的文件名,从本地集合读取对应文件并拼接成原始数据:
byte[] cmsData = fileService.getBlobByPathAsBytes(docId, NuxeoConstants.PROPERTY_SIGNATURES_FILES, index, client); CMSSignedData cmsSignedData = new CMSSignedData(cmsData); SignerInformationStore signerInformationStore = cmsSignedData.getSignerInfos(); SignerInformation signerInformation = (SignerInformation) signerInformationStore.getSigners().stream().findFirst().get(); Attribute signedFileNamesAttr = signerInformation.getSignedAttributes().get(new DERObjectIdentifier(FILE_NAMES_ATTR)); DERSet derSet = (DERSet) signedFileNamesAttr.getAttrValues(); String signedFileNamesStr = String.valueOf(derSet.getObjectAt(0)); String[] signedFileNames = signedFileNamesStr.split("<>"); ByteArrayOutputStream allFileBytes = new ByteArrayOutputStream(); for (String signedFileName : signedFileNames) { if (!files.containsKey(signedFileName)) { throw new RedoException(String.format("Не найден файл %s для проверки подписи", signedFileName)); } allFileBytes.write(files.get(signedFileName)); } byte[] srcData = allFileBytes.toByteArray();
尝试的构造逻辑
尝试通过原始数据重新构造包含内容的CMSSignedData:
CMSSignedData myCMS = CMSUtil.parseAsCMS(cmsData); boolean isExternal = myCMS.getSignedContent() == null; if (isExternal) { CMSProcessableByteArray data = new CMSProcessableByteArray(srcData); myCMS = new CMSSignedData(data, cmsData); }
正确实现方案
你的核心思路是正确的,分离式CMS签名本身就是签名数据与原始内容分离存储,要获取完整附加内容,只需将原始内容与签名数据重新绑定,注意以下关键细节:
- 确保原始内容的字节顺序、拼接方式完全和签名时一致,否则会导致签名验证失败,也无法生成正确的完整CMS实例。
- 用
CMSProcessableByteArray封装原始数据,通过CMSSignedData的双参构造器(传入原始内容和签名数据)重新构造实例,新实例将包含完整的签名与对应内容。 - 可选:验证签名有效性,构造完成后可调用
verifySignatures()确认原始内容与签名匹配。
优化后的完整代码示例:
// 1. 读取签名数据并解析分离式CMS实例 byte[] cmsData = fileService.getBlobByPathAsBytes(docId, NuxeoConstants.PROPERTY_SIGNATURES_FILES, index, client); CMSSignedData detachedCMS = new CMSSignedData(cmsData); // 2. 从签名属性提取文件名并拼接原始内容 SignerInformation signerInfo = detachedCMS.getSignerInfos().getSigners().stream() .findFirst() .orElseThrow(() -> new IllegalStateException("未找到签名信息")); Attribute fileNameAttr = signerInfo.getSignedAttributes() .get(new DERObjectIdentifier(FILE_NAMES_ATTR)); if (fileNameAttr == null) { throw new IllegalStateException("签名属性中未包含文件名信息"); } DERSet fileNameSet = (DERSet) fileNameAttr.getAttrValues(); String fileNameStr = fileNameSet.getObjectAt(0).toString(); String[] signedFileNames = fileNameStr.split("<>"); ByteArrayOutputStream contentStream = new ByteArrayOutputStream(); for (String fileName : signedFileNames) { byte[] fileBytes = files.get(fileName); if (fileBytes == null) { throw new RedoException(String.format("未找到用于验证签名的文件:%s", fileName)); } contentStream.write(fileBytes); } byte[] originalContent = contentStream.toByteArray(); // 3. 重新构造包含完整内容的CMSSignedData实例 CMSProcessableByteArray processableContent = new CMSProcessableByteArray(originalContent); CMSSignedData completeCMS = new CMSSignedData(processableContent, cmsData); // 可选:验证签名有效性(需引入BouncyCastle提供者) completeCMS.verifySignatures(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build());
注意事项
- 确保使用的BouncyCastle版本兼容,避免API差异问题。
- 文件名分割符
"<>"需和签名时使用的保持一致,否则无法正确拆分文件名。 - 多文件拼接时,顺序必须和签名时完全相同,否则签名验证会失败。
内容的提问来源于stack exchange,提问作者Beigut Beisenkhan
相关产品推荐
相关产品推荐

