使用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 cms或openssl 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 cms和openssl 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.pem和ca_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