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

基于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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 12:15:02