跳转到内容
getnextpdf.com

Enterprise 版本

硬件安全模块签名(PKCS#11)

NextPDF Enterprise 用一把保存在硬件安全模块(HSM)内部的密钥对一份 PDF 签名。你把签名器指向一个 PKCS#11 令牌——一张智能卡、一个通用串行总线(USB)令牌,或一个网络挂载的 HSM——签名操作在该设备上运行。私钥永不离开令牌边界。本页为行为层面:它说明签名器做什么、你提供什么,以及密钥保管在何处不再是 NextPDF 的责任。

HSM 签名器通过 Core 签名器契约解析,因此你的应用程序依赖的是契约,而非具体的 Enterprise 类型。它扩展了 Core 所用的同一条密码消息语法(CMS)签名路径,只不过密码学运算被委托给令牌。

前提条件已在前置数据中陈述,并在前提条件处重述,以免你在任务进行中措手不及。

该能力随 NextPDF Enterprisenextpdf/enterprise)发行,并以一份 Enterprise 层级的授权信封激活。没有该权益的部署不会加载该能力的类。比较各版本并获取授权

NextPDF Core 发行一个把密钥保存在进程内、或通过 Core 签名策略契约接受密钥的软件 CMS 签名器;NextPDF Pro 增加了远程与云密钥管理服务(KMS)签名策略。通过 PKCS#11 的硬件密钥保管是一项 Enterprise 能力,Core 或 Pro 不提供。

一个 PKCS#11 令牌在一个厂商共享库之后暴露密码学对象——证书与私钥。Enterprise 签名器适配那个库:

  1. 它每进程一次地打开令牌的共享库并缓存模块句柄,因为 PKCS#11 要求每进程恰好初始化模块一次。
  2. 它在所配置的插槽上打开一个会话,并用所提供的 PIN 登录。依据 PKCS#11 v3.1 §5.6.8,登录会在任何私钥操作之前认证用户。
  3. 它按标签在令牌上定位签名证书、以可区别编码规则(DER)形式读取证书,并检测公钥算法。
  4. 在签名时它按标签定位私钥——在某些令牌上其标签可能与证书标签不同——并请求令牌计算签名。待签名的数据被传入;密钥留在设备上。

签名器支持带 PKCS#1 v1.5 填充的 RSA(SHA-256、SHA-384、SHA-512)、带概率签名方案(PSS)填充且盐长等于摘要长度的 RSA,以及带 SHA-256、SHA-384 与 SHA-512 的椭圆曲线数字签名算法(ECDSA)。ECDSA 曲线与摘要按惯例配对——P-256 配 SHA-256、P-384 配 SHA-384、P-521 配 SHA-512——遵循 RFC 5480 中的推荐配对。一个令牌以两个整数的原始拼接形式返回一个 ECDSA 签名;签名器把它转换为 PDF 与 OpenSSL 所期望的 DER 编码形式。

对于签名生成,依据 NIST SP 800-131A Rev.2 §3,至少 2048 位的 RSA 密钥与至少 224 位阶的 ECDSA 曲线是可接受的最低限。请把你的令牌密钥预置到那些大小或以上。

存在一条替代的 OpenSSL 引擎路径,用于引擎支撑的令牌。在 OpenSSL 3.x 上,PHP OpenSSL 扩展不暴露引擎应用程序编程接口(API),因此引擎类已弃用;受支持的引擎支撑路线运行 OpenSSL 命令行二进制。在你的令牌具有 PKCS#11 库之处,请优先选择直接的 PKCS#11 路径。

起决定性作用的决策是私钥永不离开令牌。因此签名器把密码学运算委托给设备,只把待签名数据跨越 PKCS#11 接缝传递。它绝不在 PHP 内存中读取或重建密钥素材。它通过 Core HsmSignerInterface 契约而非一个具体的 Enterprise 类型解析,因此无论密钥存于软件、云 KMS 还是硬件令牌,签名代码都完全相同。它每进程一次地缓存模块句柄,因为 PKCS#11 对每个模块每进程恰好初始化一次,随后把令牌的原始 ECDSA 输出转换为 DER,使验证器看到它们所期望的编码。驱动其形态的是保管而非便利:信任边界始终停留在设备边缘。

设计背景:HSM 支撑的签名

在你用 HSM 签名之前,请确认每一项:

  1. 安装 NextPDF Core 与 Enterprise 包:composer require nextpdf/core:^3composer require nextpdf/enterprise
  2. 持有一份有效的 NextPDF Enterprise 授权;在 Private Packagist 上用你的授权凭据解析该包。
  3. 在主机上安装令牌厂商的 PKCS#11 共享库(例如 Linux 上的 .so 或 Windows 上的 .dll),并记下它的绝对路径、插槽号,以及对象标签。
  4. 加载 ext-pkcs11 PHP 扩展。它不随标准 PHP 捆绑,必须单独安装。当该扩展缺失时,签名器构造函数会抛出一个有类型的操作错误。

向签名器提供这些输入:

  • 库路径 —— 厂商 PKCS#11 共享库的绝对路径。
  • 插槽标识符 —— 令牌插槽号,通常为 0
  • PIN —— 令牌 PIN。请把它当作机密:从你的机密管理器提供它,绝不要从源代码或日志提供。签名器把 PIN 参数标记为敏感,使其被排除在堆栈跟踪与序列化之外。
  • 证书标签 —— 令牌上证书对象的标签。
  • 密钥标签 —— 当私钥对象的标签与证书标签不同时,其标签。
  • —— 当令牌不持有时,以 DER 形式提供的可选中间证书。

请在构造签名器之前检查令牌可用性。构造会从令牌读取证书,因此一个配置错误的插槽或标签会以一个有类型的错误快速失败,而非在签名时才失败。

  1. 通过检查扩展可用性,确认运行时支持 PKCS#11。当该扩展缺失时,不要构造签名器。
  2. 把 PIN 从你的机密管理器读入一个绝不被记录的变量。
  3. 用库路径、插槽、PIN 与各标签构造 HSM 签名器。构造会登录并读取证书。
  4. 通过 HsmSignerInterface 把签名器传给 Core 签名编排器。编排器计算字节范围、构建 CMS 签名属性、把数据交给令牌,并组装签名后的 PDF。
  5. 捕获最具体的失败,记录一条不含 PIN 的结构性消息,然后重新抛出。
examples/contracts/hsm-signer-availability.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Contracts\HsmSignerInterface;
/**
* Build a hardware-token signer only when the runtime supports it.
*
* The concrete PKCS#11 signer is resolved through the Core contract so the
* caller depends on the interface, not the Enterprise implementation type.
* The PIN arrives from a secret resolver; it is never written to source.
*
* @param callable(): bool $pkcs11Available Reports ext-pkcs11 availability.
* @param callable(): HsmSignerInterface $signerFactory Builds the configured token signer.
*
* @throws \RuntimeException When the PKCS#11 extension is not loaded.
*
* @return HsmSignerInterface The token signer, ready for the Core orchestrator.
*/
function resolveHsmSigner(callable $pkcs11Available, callable $signerFactory): HsmSignerInterface
{
if ($pkcs11Available() !== true) {
throw new \RuntimeException(
'PKCS#11 signing requires the ext-pkcs11 extension; install it before signing.',
);
}
return $signerFactory();
}

生产接线——确切的构造参数列表与有类型的异常类型——记录在 HSM 深度参考中。

examples/contracts/hsm-sign-guarded.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Contracts\HsmSignerInterface;
use NextPDF\Exception\NextPdfException;
use Psr\Log\LoggerInterface;
final readonly class HsmSigningService
{
public function __construct(
private HsmSignerInterface $signer,
private LoggerInterface $logger,
) {}
/**
* Sign data on the token through the Core HSM contract.
*
* The byte range is computed by the engine, never accepted from the
* caller. The token performs the signing operation; the private key
* does not leave the device.
*
* @param string $data The bytes the orchestrator hands to the token.
* @param string $algorithm The OpenSSL-style signing algorithm identifier.
*
* @throws NextPdfException When the token operation fails.
*
* @return string The raw signature bytes returned by the token.
*/
public function sign(string $data, string $algorithm): string
{
try {
return $this->signer->sign($data, $algorithm);
} catch (NextPdfException $e) {
// Structural message only — never the PIN or key material.
$this->logger->error('HSM signing failed', ['reason' => $e->getMessage()]);
throw $e;
}
}
}

像一个验证者那样确认结果:

  1. 从签名器以 DER 形式读回签名者证书与链,并确认它们与令牌上所预置的证书相符。
  2. 在一个配置了你的信任锚的验证器中打开签名后的 PDF,并确认该签名被报告为密码学上完好。一个生成的签名不是一个已核验的签名;信任决定属于验证者及其信任锚,而非属于生产者。
  3. 对于一个 ECDSA 签名,确认所内嵌的签名是 DER 编码的——签名器已为你转换了令牌的原始输出,因此一个拒绝原始拼接形式的验证器仍应接受所内嵌的签名。
  4. 确认你的应用程序日志中不出现任何 PIN、令牌标签或密钥素材。
  • 密钥留在令牌上。 待签名的数据被交给令牌;签名操作在令牌边界内部运行。私钥绝不会被加载进 PHP 内存。
  • PIN 是机密。 它是一个敏感的构造参数,被排除在日志与序列化之外。请从一个机密管理器提供它。反复失败的重新认证可能在令牌处锁定 PIN;强制执行该策略的是令牌,而非 NextPDF。
  • 失败关闭。 一个令牌或 HSM 错误会抛出一个有类型的异常。签名器不会产生一个未签名或部分签名的结果,也绝不替换为一个更弱的算法。
  • 算法强度。 请预置至少 2048 位的 RSA 密钥与至少 224 位阶的 ECDSA 曲线,依据 NIST SP 800-131A Rev.2 §3,这是签名生成可接受的最低限。
  • 后量子签名是实验性的且默认关闭。 一条后量子路径存在于一个显式的选择启用标志之后。标准 PDF 高级电子签名(PAdES)长期归档配置文件尚不识别后量子套件,且大多数查看器在验证时拒绝它们。请不要为生产 PAdES 签名启用它。

本页涉及密码学签名与硬件安全模块集成。每一项规范性来源均以转述方式呈现;不复现任何规范性原文。### 密钥保管边界

NextPDF Enterprise 与一个 PKCS#11 令牌或 HSM 集成。它不存储、不生成签名密钥,也不保证其安全性。密钥安全取决于令牌或 HSM、部署,以及操作方——而不仅仅取决于 NextPDF Enterprise。你负责令牌预置、PIN 处理、插槽配置,以及一个网络挂载 HSM 的网络保护。

  • 扩展缺失。ext-pkcs11 未加载时,构造 PKCS#11 签名器会抛出一个有类型的操作异常。请先检查可用性。
  • 按标签找不到证书或密钥。 构造或签名会抛出一个有类型的异常,并指明缺失的对象。请确认标签与插槽。
  • 已经登录。 当多个签名器实例为同一个插槽共享一个被缓存的模块时,签名器会登出并重新登录,以提供一次新鲜的 PIN 验证——这是带“每次都需 PIN”策略的个人身份验证令牌所要求的。
  • 不受支持的算法。 请求一个签名器未映射的算法会抛出一个参数错误,而非用一个替代算法签名。
  • 网络 HSM 不可达。 一个网络或设备错误会抛出一个有类型的异常;签名器绝不会静默地产生一份未签名的文档。

本页仅记录外部可观察的行为与受支持的公共 API 接口。内部命名空间路径、辅助类、机制表、运行手册文件名与工单前缀均不在范围内。