按产品分类浏览文章 关于本站
- 目录 -

使用CloudHSM完成PKCS#7分离签名(Detached Signature)

本文介绍如何使用AWS CloudHSM完成PKCS#7分离签名。首先在本地用OpenSSL跑通完整的PKCS#7签名流程,理解密钥生成、证书签发和签名验证等步骤,然后将签名者私钥迁移到CloudHSM加密机内保护,通过JCE Provider调用CloudHSM完成签名运算,私钥不离开硬件。文中提供了Java Internal API和BouncyCastle两种实现方式的完整代码示例。

一、背景

1、什么是PKCS#7

PKCS#7(也称 CMS,Cryptographic Message Syntax)是一种广泛使用的加密消息语法标准,用于数字签名、加密等场景。本文介绍如何基于CloudHSM加密机完成PKCS#7签名算法。

分离签名(Detached Signature) 是 PKCS#7 签名的一种模式,其特点是:

  • 签名结果(.p7s 文件)中 不包含原始数据,仅包含签名值、签名者证书和摘要信息
  • 原始数据文件保持独立,验证时需同时提供原始数据和 .p7s 签名文件
  • 适用于 OTA 固件签名、代码签名等需要保持原始文件完整性的场景

本文档使用的分离签名参数:

参数
签名算法 ECDSA with SHA-256/384/512(P-256/384/512 椭圆曲线)
摘要算法 SHA-256/384/512
签名类型 Detached Signature
输出格式 PKCS#7 (.p7s),PEM (Base64编码)或 DER 格式

2、如何基于CloudHSM实现PKCS#7签名

PKCS#7的完整过程可以由OpenSSL库在开发者本地通过Shell脚本完成,这个实现过程参考本文第二章节。如果考虑到密钥被放在CloudHSM内保护,希望CloudHSM直接提供签名,那么会遇到如下问题:

根据AWS官方文档确认,CloudHSM Client SDK 5不支持直接生成PKCS#7(CMS)签名。

SDK 5 支持的签名机制仅限于底层的原始签名操作:

  • RSA 签名:CKM_RSA_PKCS、CKM_RSA_PKCS_PSS、CKM_SHA*_RSA_PKCS 等
  • ECDSA 签名:CKM_ECDSA、CKM_ECDSA_SHA* 等
  • HMAC / CMAC PKCS#7/CMS 是一种高层的签名封装格式(包含签名者证书链、签名属性、内容封装等),CloudHSM 本身只提供底层密码学原语,不处理这种高层结构。

在CloudHSM上实现PKCS#7签名的常见做法:

  • JCE Provider 方案(Java):用 CloudHSM JCE Provider 作为签名引擎,使用Java internal API实现,或者使用Bouncy Castle的 CMSSignedDataGenerator来组装 PKCS#7 结构。私钥留在 HSM 中,签名操作通过 JCE 委托给 CloudHSM。这个实现参考本文第三章节
  • OpenSSL Engine 方案:用CloudHSM的OpenSSL Dynamic Engine实现与OpenSSL的集成,然后在通过Shell脚本执行openssl cmsopenssl smime命令生成 PKCS#7签名,签名操作会自动委托给 HSM。注意:本方法是Shell脚本实现,不是Java代码实现。
  • PKCS#11 方案(C/Python 等):通过 PKCS#11 接口获取签名结果,然后用 cryptography/pyOpenSSL/asn1crypto 等库手动构建PKCS#7/CMS 结构。注意:本方法是C/C++/Python代码实现,不是Java代码实现。

总结以上几种方式,使用Java是相对最广泛的,适合场景最多。

二、在开发者本地使用OpenSSL库完成PKCS#7签名(CMS算法)的全流程

由于PKCS#7涉及很多加密算法知识,为了降低上手过程,这里先完全脱离CloudHSM,在开发者本机使用OpenSSL库跑完一个完整的PKCS#7签名,以便于理解相关名称、术语、参数。在流程跑通后,将这个流程中的关键密钥放到CloudHSM进行保护。

本章节为开发者本地使用OpenSSL库完成PKCS#7签名的全流程。

1、生成非对称密钥

(1) 生成 CA 私钥

openssl ecparam -genkey -name secp384r1 -noout -out ca_key.pem

(2) 生成签名者私钥

openssl ecparam -genkey -name secp384r1 -noout -out signer_key.pem

参数说明:

  • -name secp384r1:指定 P-384 曲线
  • -noout:不输出 EC 参数,仅输出密钥
  • -genkey:生成密钥

2、创建自签名 CA 证书

openssl req -new -x509 -key ca_key.pem -sha384 -days 3650 \
  -subj "/C=CN/ST=BJ/L=BEIJING/O=BITIPCMAN/OU=DEV/CN=dev.bitipcman.com/emailAddress=dev_admin@bitipcman.com" \
  -out ca_cert.pem

参数说明:

  • -x509:直接输出自签名证书(而非 CSR)
  • -sha384:使用 SHA-384 摘要算法
  • -days 3650:证书有效期 10 年
  • -subj:CA 的主题信息

创建完成后,可使用如下命令验证:

openssl x509 -in ca_cert.pem -noout -text

3、使用 CA 签发签名者证书

(1) 生成签名者证书签名请求(CSR)

openssl req -new -key signer_key.pem -sha384 \
  -subj "/C=CN/ST=BJ/L=BEIJING/O=BITIPCMAN/OU=OPS/CN=ops.bitipcman.com/emailAddress=ops_admin@bitipcman.com" \
  -out signer_csr.pem

(2) 使用 CA 签发证书

openssl x509 -req -in signer_csr.pem \
  -CA ca_cert.pem -CAkey ca_key.pem -CAcreateserial \
  -sha384 -days 365 \
  -out signer_cert.pem

参数说明:

  • -CA / -CAkey:指定 CA 证书和私钥
  • -CAcreateserial:自动创建序列号文件
  • -days 365:签名者证书有效期 1 年

验证证书链:

openssl verify -CAfile ca_cert.pem signer_cert.pem

4、在本地使用OpenSSL库执行PKCS#7分离签名算法并输出为PEM格式

(1) OpenSSL实现方式

在Openssl库实现PKCS#7分离的选择上,有两个参数,一个是cms参数,一个是smime参数。二者区别如下:

openssl smime openssl cms
标准 基于旧的 PKCS#7 / S/MIME v2
状态 旧版,已不推荐
算法支持 有限(不支持 AEAD 等新算法)
功能 基本的签名、加密、验证
输出格式 PKCS#7
兼容性 与老系统兼容好

两个参数生成的证书二进制文件是兼容的,如果是输出Base64编码的PEM格式,那么他们的证书本体是兼容的。

(2) 准备要签名的文件

准备一个待签名文件。这里演示的被加密的文件内容是文本,但后续将直接把这个文件作为二进制来处理,其文件本身是否是可编辑的文本文件并不重要,它可以是任意文本、图像、音视频、压缩包等。签名时候直接将此文件作为二进制来处理。

echo "OTA firmware payload data" > firmware.bin

(3) 完成签名

执行如下命令完成签名。

openssl cms -sign -binary -noattr \
  -in firmware.bin \
  -signer signer_cert.pem -inkey signer_key.pem \
  -certfile ca_cert.pem \
  -md sha384 \
  -nosmimecap \
  -outform PEM \
  -out firmware.p7s

关键参数说明:

参数 说明
-sign 执行签名操作
-binary 以二进制模式处理输入,不做 MIME 文本转换(对固件等二进制文件必须加此参数)
-noattr 不添加签名属性(如签名时间等),生成的即为分离签名
-signer 签名者证书
-inkey 签名者私钥
-certfile 附加的 CA 证书,便于验证方构建证书链
-md sha384 指定摘要算法为 SHA-384
-nosmimecap 不包含 S/MIME Capabilities 属性
-outform PEM PEM 输出 Base64 编码

注意openssl cmsopenssl smime 默认生成的就是分离签名(Detached),原始数据不会被嵌入 .p7s 文件中。如果需要将数据嵌入签名,需显式加 -nodetach 参数。

由此获得firmware.p7s就是分离签名,此文件只包含签名,不含被签名文件原文。

(4) 查看签名文件的结构

执行如下命令查看PKCS#7结构信息:

openssl cms -cmsout -print -in firmware.p7s -inform PEM

注意:这一步只是查看文件完整性,并不是执行签名校验的算法。

5、验证分离签名

签名完成后,对用户Public公开可见的文件是CA证书(ca_cert.pem)、签名者证书(signer_cert.pem,被嵌入签名文件内)、下载软件包(firmware.bin)以及签名文件(firmware.p7s,内含签名者证书)。

执行如下命令验证签名。

openssl cms -verify -binary \
  -in firmware.p7s -inform PEM \
  -content firmware.bin \
  -CAfile ca_cert.pem \
  -out /dev/null

参数说明:

  • -content firmware.bin:指定原始数据文件(分离签名验证时必须提供)
  • -in firmware.p7s:输入的签名文件名
  • -inform PEM:输入格式为PEM格式
  • -CAfile:指定信任的 CA 证书
  • -out /dev/null:丢弃提取的内容输出

验证成功会输出:

Verification successful

6、将CMS输出的证书格式转为PKCS#7格式

OpenSSL的cms参数和pkcs参数,生成的文件的密钥本地是二进制兼容的,但是开头和末尾会有-----BEGIN CMS----------END CMS-----的字样。如果现有应用只识别-----BEGIN PKCS7----------END PKCS7-----的标签,那么可以做下转换。密文本体不需要转换,只是修改文件的头:

sed 's/BEGIN CMS/BEGIN PKCS7/;s/END CMS/END PKCS7/' firmware.p7s > firmware_pkcs7.p7s

如果证书文件开头和末尾是PKCS7格式,那么校验命令也要修改:

openssl smime -verify -binary \
  -in firmware_pkcs7.p7s -inform PEM \
  -content firmware.bin \
  -CAfile ca_cert.pem \
  -out /dev/null

返回Verification successful即表示校验成功。

7、使用OpenSSL库做PKCS#7分离签名(Detached Signature)的常见问题

Q1:签名文件PEM与DER格式如何相互转换

签名文件DER格式转PEM格式:

openssl cms -cmsout -in firmware.p7s -inform DER -outform PEM -out firmware_pem.p7s

签名文件PEM格式转DER格式:

openssl cms -cmsout -in firmware_pem.p7s -inform PEM -outform DER -out firmware.p7s

**Q2:验证时遇到certificate verify failed**错误

原因:CA 证书不在信任链中。确保 -CAfile 指向的是签发签名者证书的 CA 证书。如有多级 CA,需将完整证书链合并到一个文件中:

cat intermediate_ca.pem root_ca.pem > ca_chain.pem
openssl cms -verify -binary -in firmware.p7s -inform DER -content firmware.bin -CAfile ca_chain.pem

Q3:签名或验证二进制文件时内容被篡改

原因:OpenSSL执行签名或者验证时候未加-binary参数,OpenSSL默认会对输入做CRLF文本规范化处理。为将被签名的文件视作二进制文件,而不需要OpenSSL识别文件类型,加-binary参数强制处理即可。

Q4:如何确认签名文件是原始文件和签名文件分类的Detached类型?

使用以下命令查看PKCS#7结构。

openssl cms -cmsout -print -in firmware.p7s -inform PEM | grep -A2 "encapContentInfo"

返回结果如下:

    encapContentInfo: 
      eContentType: pkcs7-data (1.2.840.113549.1.7.1)
      eContent: <ABSENT>

如果encapContentInfo中不包含eContent字段,即显示eContent: <ABSENT>,则表示当前PEM文件中没有原始文件内容,也就是本PEM文件是纯签名文件。

三、在CloudHSM上完成PKCS#7分离签名步骤和代码示例

1、将关键签名步骤转移到云端的CloudHSM加密机

本章节描述如何将PKCS#7分离签名流程与CloudHSM结合,将签名者私钥保护在加密机内,签名运算由CloudHSM完成,私钥明文不离开加密机。

(1) 基础环境准备

与前一章节用纯OpenSSL实现所有流程的核心区别:

对比项 纯 OpenSSL 方案 CloudHSM 方案
签名者私钥存储 本地文件系统(signer_key.pem) CloudHSM 内部,不可导出
签名运算执行位置 本地 OpenSSL CloudHSM 硬件
CA 私钥存储 本地文件系统 管理员/开发者的本地文件系统
CSR 生成 本地 OpenSSL 使用 CloudHSM 内私钥签名 CSR

前置条件:

  • AWS CloudHSM 集群:已初始化并处于 ACTIVE 状态
  • CloudHSM Client SDK 5:已安装并配置连接到集群,注意一定是SDK版本5,SDK版本3在2026年End-of-life
  • Crypto User (CU):已在 CloudHSM 中创建用于密钥管理和签名的一般权限用户,CloudHSM的Admin用户只用于管理,不能用于日常密钥操作
  • Java 环境:使用 CloudHSM JCE Provider SDK,JDK 8 则需要使用BouncyCastle库,JDK 11 以上可不使用BouncyCastle库,使用Java Internal API替代但需要显式配置开关。

本文为了保持对现有系统兼容性,将上文OpenSSL Demo中的P384改为P256算法,后续代码中将使用P256算法,实际上生产可根据需求灵活调整。

(2) 样例代码以及关于是否使用BouncyCastle库

BouncyCastle库提供了常用的加密算法的封装,可简化开发过程。不过老版本的BouncyCastle可能存在安全隐患,需要定期升级到最新版本。如果不想做类似升级,那么可以规避使用BouncyCastle库。此时可以使用Java Internal API作为替代,但有一些前提:

  • 要求Java版本>=11
  • 用纯JDK的sun.security.pkcs10.PKCS10和sun.security.x509.X500Name实现CSR生成
  • 在编译和运行时候,要加上额外的参数启用Java内部API

如果JDK版本只能是8那么就只能依赖BouncyCastle了。或者您的Java版本可以使用11或者更高版本、且不限制BouncyCastle库的使用,那么代码会更简单,不需要配置启用Java内部API的命令了。

2、关键步骤拆解

关键步骤拆解如下。

┌─────────────────────────────────────────────────────────────────┐
│  管理员本地操作(仅使用OpenSSL)                                │
│                                                                 │
│  步骤1:生成 CA 密钥对 + 自签名 CA 证书                         │
│  步骤4:用 CA 签发签名者证书                                    │
├─────────────────────────────────────────────────────────────────┤
│  管理员初始化操作(调用CloudHSM操作创建密钥、生成签名者请求)   │
│                                                                 │
│  步骤2:在 CloudHSM 内生成签名者 ECC P-256 密钥对               │
│  步骤3:通过 CloudHSM 生成签名者 CSR                            │
├─────────────────────────────────────────────────────────────────┤
│  业务用户发起签名操作(调用CloudHSM签名接口)                   │
│                                                                 │
│  步骤5:通过 CloudHSM 执行 PKCS#7 分离签名                      │
├─────────────────────────────────────────────────────────────────┤
│  最终用户校验签名操作(无须调用CloudHSM)                       │
│                                                                 │
│  步骤6:验证签名结果                                            │
└─────────────────────────────────────────────────────────────────┘

下文将按照以上6个步骤执行,每个步骤对应一个小标题。

3、【步骤1】管理员在本地使用OpenSSL库生成 CA 密钥对 + 自签名 CA 证书

关键步骤中的步骤1包含生成 CA 密钥对 + 自签名 CA 证书,这两个任务都在管理员本地使用OpenSSL库完成,无需调用CloudHSM的SDK。命令如下。

openssl ecparam -genkey -name prime256v1 -noout -out ca_key.pem
openssl req -new -x509 -key ca_key.pem -sha256 -days 3650 \
  -subj "/C=CN/ST=BJ/L=BEIJING/O=BITIPCMAN/OU=DEV/CN=dev.bitipcman.com/emailAddress=dev_admin@bitipcman.com" \
  -out ca_cert.pem

由此在管理员本机获得了ca_key.pemca_cert.pem两个文件。

4、【步骤2】管理员在CloudHSM加密机内生成非对称密钥对用于签名者

(1) 代码示例

与前文使用OpenSSL在本机完成所有密钥生成不同,这里的签名者私钥是受到CloudHSM保护的,因此需要在CloudHSM上生成。调用CloudHSM SDK 5的JCE Provider代码,简单逻辑如下:

  • 初始化 CloudHsmProvider 并注册到 JCE
  • 通过 KeyPairGenerator.getInstance(“EC”, “CloudHSM”) 创建 EC 密钥对生成器
  • 使用 KeyPairAttributesMap 配置密钥属性:
    • 曲线:EcParams.EC_CURVE_PRIME256(secp256r1 / P-256)
    • 标签:sign_key_public(公钥),以及sign_key_private(私钥)
    • TOKEN=true:密钥持久化存储在 HSM 中
    • EXTRACTABLE=false:私钥不可导出
  • 生成密钥对后,将公钥以 PEM 格式保存到 sign_key_public.pem

示例代码在本文Github仓库内:src/main/java/com/example/cloudhsm/ECCKeyPairGenerator.java

(2) 构建代码

将Github仓库的代码整体(包含pom.xml等)下载到开发环境后,确认本机已经安装好CloudHSM的Java SDK,路径是/opt/cloudhsm/java/cloudhsm-jce-5.16.2.jar

现在执行如下命令构建:

# maven install
mvn install:install-file \
  -Dfile=/opt/cloudhsm/java/cloudhsm-jce-5.16.2.jar \
  -DgroupId=com.amazonaws \
  -DartifactId=cloudhsm-jce \
  -Dversion=5.16.2 \
  -Dpackaging=jar
mvn clean package

构建完毕,在target目录下获得了jar包。

(3) 运行代码在CloudHSM内生成密钥

构建完毕后,执行如下命令运行:

# 如果您的CloudHSM是开发测试环境只有单机,那么调用CloudHSM之前,需要配置禁用集群检查的选项
sudo /opt/cloudhsm/bin/configure-jce --disable-key-availability-check
# 通过环境变量传入CloudHSM的用户名和密钥
export HSM_USER=user01
export HSM_PASSWORD=1qazxsw2
# 执行Java程序
java -jar target/ecc-keygen.jar

运行成功返回如下:

使用用户: user01 连接到CloudHSM...
ECC P-256 密钥对生成成功!
公钥已保存到: sign_key_public.pem
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgO9lpGv4V4aznr6YuBPnkscn+N6R
fV6on3YT0x9bMBZQzD5u3jr8Wz49flrcj6hr9AOsMNGxrB2EkWFtne1WZQ==
-----END PUBLIC KEY-----

由此生成了非对称密钥,且私钥保存在CloudHSM加密机内,公钥从CloudHSM加密机下载到当前目录下。

(4) 使用CloudHSM CLI登陆到CloudHSM验证密钥生成成功

连接到一台能访问CloudHSM的虚拟机上,通过CloudHSM CLI可登陆检查密钥是否创建成功。

# 如果您的CloudHSM是开发测试环境只有单机,那么调用CloudHSM之前,需要配置禁用集群检查的选项
sudo /opt/cloudhsm/bin/configure-cli --disable-key-availability-check
/opt/cloudhsm/bin/cloudhsm-cli interactive

启动CloudHSM CLI后,输入如下命令完成登陆并查看密钥:

login --username user01 --role crypto-user
key list

即可打印所有密钥的信息。

上文的Java代码中,创建密钥时候预设了Label标签是sign_key_private,因此也可以增加过滤条件进行查询。

key list --filter attr.label=sign_key_private --verbose

如果希望删除测试期间不用的密钥,可以通过如下命令:

key delete --filter key-reference=0x0000000000103928

即可删除不需要的密钥。

5、【步骤3】管理员在CloudHSM加密机内生成签名者的CSR

(1) CSR原理

CSR(Certificate Signing Request,证书签名请求)是向 CA 申请证书时提交的一份"申请表"。CSR中包含:

  • 申请者的公钥
  • 申请者的身份信息(C、O、CN 等)
  • 申请者用自己的私钥对以上内容做的签名(证明"我确实拥有这个公钥对应的私钥")

上一步中,我们已经为签名者生成了公钥(开发者本地和CloudHSM内都有)和私钥(私钥仅在CloudHSM内受到保护),现在我们来生成CSR。

(2) 查询上一步生成的密钥

在使用User身份登陆到CloudHSM CLI成功后,执行如下命令:

key list --filter attr.label=sign_key_private
key list --filter attr.label=sign_key_public

由于CloudHSM内保存密钥时候,Label标签是允许重名的,但是Key Reference ID是唯一的。本文后续代码使用Label作为输入简化开发,因此如果有多个密钥Label一样,请删除不使用的密钥。删除命令和参数写法在本文的上一个章节。确保Label查询出来的Key是唯一的后,继续下文。

(3) 生成CSR

示例代码在本文Github仓库内:

  • 使用Java Internal API的版本:PKCS7-internal-api/src/main/java/com/example/cloudhsm/CSRGenerator.java
  • 使用BouncyCastle的版本:PKCS7-BouncyCastle/src/main/java/com/example/cloudhsm/CSRGenerator.java
mvn clean package

如果是希望规避BouncyCastle库的依赖,用纯JDK的sun.security.pkcs10.PKCS10和sun.security.x509.X500Name实现CSR生成,而这是Java内部API,所以需要增加--add-exports参数。

java --add-exports java.base/sun.security.pkcs10=ALL-UNNAMED \
     --add-exports java.base/sun.security.x509=ALL-UNNAMED \
     -jar target/csr-generator.jar

如果不使用Java内部API而是使用BouncyCastle库,那么可更简单:

java -jar target/csr-generator.jar

返回结果如下:

使用用户: user01 连接到CloudHSM...
已找到私钥: sign_key_private
已找到公钥: sign_key_public
CSR 已生成并保存到: signer_csr.pem
Subject: CN=ops.bitipcman.com, OU=OPS, O=BITIPCMAN, L=BEIJING, ST=BJ, C=CN, EMAILADDRESS=ops_admin@bitipcman.com

由此在当前目录下获得signer_csr.pem是签名者的CSR。

6、【步骤4】管理员在本地用OpenSSL和CA签发签名者证书

上一步获得了签名请求signer_csr.pem文件,将这个文件放到和管理员/开发者本机的CA在一起的目录,用OpenSSL完成证书签署。

执行命令如下:

openssl x509 -req -in signer_csr.pem \
  -CA ca_cert.pem -CAkey ca_key.pem -CAcreateserial \
  -sha256 -days 365 \
  -out signer_cert.pem

返回结果如下:

Certificate request self-signature ok
subject=emailAddress = ops_admin@bitipcman.com, C = CN, ST = BJ, L = BEIJING, O = BITIPCMAN, OU = OPS, CN = ops.bitipcman.com

由此,在当前目录下获得了签名者证书signer_cert.pem

7、【步骤5】通过 CloudHSM 执行 PKCS#7 分离签名

通过以上步骤,我们已经完成了准备工作。接下来终于可以做签名了。PKCS#7 分离签名需要的材料:

材料 来源 说明
签名者私钥 CloudHSM 内 通过 key-reference 或 label 引用,不可导出
签名者证书 signer_cert.pem 本地文件 CA 签发的证书,嵌入 PKCS#7 结构中
CA 证书 ca_cert.pem 本地文件 可选,嵌入证书链便于验证方校验
待签名文件(如 firmware.bin) 本地文件 被签名的原始数据

代码逻辑如下:

  • 通过 KeyStoreWithAttributes 以标签 sign_key_private 查找 CloudHSM 内的私钥
  • 用 CertificateFactory 加载 signer_cert.pem 和 ca_cert.pem
  • 读取 firmware.bin,计算 SHA-256 摘要
  • 手动构建 PKCS#7 SignedData ASN.1 DER 结构(使用 sun.security.util.DerOutputStream):
    • 分离签名模式(contentInfo 中不嵌入原始数据)
    • 签名属性包含 contentType、signingTime、messageDigest
    • 摘要算法 SHA-256,签名算法 ECDSA with SHA-256
    • 包含签名者证书和 CA 证书
  • 通过 CloudHSM JCE Provider 执行 SHA256withECDSA 签名
  • 输出 PEM 编码的 firmware.p7s

示例代码在本文Github仓库内:

  • 使用Java Internal API的版本:PKCS7-internal-api/src/main/java/com/example/cloudhsm/PKCS7Signer.java
  • 使用BouncyCastle的版本:PKCS7-BouncyCastle/src/main/java/com/example/cloudhsm/PKCS7Signer.java

构建:

mvn clean package

使用Java Internal API的版本命令如下:

java --add-opens java.base/sun.security.util=ALL-UNNAMED \
  -jar target/pkcs7-signer.jar

使用BouncyCastle的版本命令如下:

java -jar target/pkcs7-signer.jar

执行结果如下:

使用用户: user01 连接到CloudHSM...
已找到私钥: sign_key_private
已加载签名者证书: CN=ops.bitipcman.com, OU=OPS, O=BITIPCMAN, L=BEIJING, ST=BJ, C=CN, EMAILADDRESS=ops_admin@bitipcman.com
已加载CA证书: EMAILADDRESS=dev_admin@bitipcman.com, CN=dev.bitipcman.com, OU=DEV, O=BITIPCMAN, L=BEIJING, ST=BJ, C=CN
已读取待签名文件: firmware.bin (26 bytes)
签名完成,签名长度: 70 bytes
PKCS#7 签名已保存到: firmware.p7s

由此获得了firmware.p7s就是签名文件。

显示firmware.p7s内容可使用如下命令(注意如下命令只是显示签名内容,不是校验签名)。

openssl cms -cmsout -print -in firmware.p7s -inform PEM

8、【步骤6】验证签名结果

签名验证过程是不需要连接到CloudHSM的,基于签名文件p7s、以及签名文件内置的签名者证书signer_cert.pem,还有CA证书ca_cert.pem即可完成验证。

通过OpenSSL校验签名的命令如下:

openssl smime -verify -binary \
  -in firmware.p7s -inform PEM \
  -content firmware.bin \
  -CAfile ca_cert.pem \
  -out /dev/null

返回结果Verification successful表示验证成功。

除了使用OpenSSL和Shell脚本验证签名外,还可以使用Java代码执行。从如下网址获取样例代码:

  • 使用Java Internal API的版本:PKCS7-internal-api/src/main/java/com/example/cloudhsm/PKCS7Verifier.java
  • 使用BouncyCastle的版本:PKCS7-BouncyCastle/src/main/java/com/example/cloudhsm/PKCS7Verifier.java

构建:

mvn clean package

由于不需要连接到CloudHSM,因此本步骤无须设置CloudHSM的用户名密码,可在任何客户端位置执行验证。

使用Java Internal API的版本执行如下:

java --add-exports java.base/sun.security.pkcs=ALL-UNNAMED \
     --add-exports java.base/sun.security.util=ALL-UNNAMED \
     -jar target/pkcs7-verifier.jar firmware.p7s firmware.bin ca_cert.pem

使用BouncyCastle的版本执行如下命令:

java -jar target/pkcs7-verifier.jar

返回结果如下:

签名验证成功 ✓
证书链验证成功 ✓
签名者: CN=ops.bitipcman.com, OU=OPS, O=BITIPCMAN, L=BEIJING, ST=BJ, C=CN, EMAILADDRESS=ops_admin@bitipcman.com
CA: EMAILADDRESS=dev_admin@bitipcman.com, CN=dev.bitipcman.com, OU=DEV, O=BITIPCMAN, L=BEIJING, ST=BJ, C=CN
Verification successful

由此校验PKCS#7签名完成。

四、参考文档

OpenSSL Dynamic Engine for AWS CloudHSM Client SDK 5

https://docs.aws.amazon.com/cloudhsm/latest/userguide/openssl-library.html


最后修改于 2026-03-16

- 目录 -