该页面可以包含自动翻译的文本。

如何使用 USB 令牌和 HSM 设备在 C# 和 VB.NET 中为 PDF 签名

受信任的证书颁发机构不再允许导出用于文档签名证书的私钥。相反,私钥必须存储在安全硬件中,例如 USB 令牌、智能卡或基于云的 HSM。

你可以使用 Docotic.Pdf 库 在 .NET 中使用 HSM 设备为 PDF 文档签名。本文介绍与 Azure Key Vault、AWS KMS 和符合 PKCS#11 的硬件的集成。

你可以在 下载 C# .NET PDF 库 页面获取该库和一个有时间限制的免费许可证密钥。

外部 PDF 签名

先决条件

本文中的代码示例使用自签名证书为 PDF 文档签名。默认情况下,PDF 查看器不信任这些签名。自签名证书应仅用于测试,不应用于生产环境。要生成受信任的 PDF 签名,请使用由第三方证书颁发机构签发的文档签名证书。

获取合适的证书

优先选择来自列于 Adobe Approved Trust List (AATL) 的公司的证书。Adobe Acrobat 默认信任此类公司的证书,并且不会报告“证书有效性未知”警告。

要创建符合欧盟(eIDAS)要求的文档,你需要使用列于 European Union Trusted Lists (EUTL) 中的提供商签发的合格证书。

了解如何在 .NET 中使用你的证书

当你订购文档签名证书时,通常会获得对 HSM 设备的访问权限。这可以是物理 USB 令牌,也可以是基于云的 HSM 解决方案。

然后你应该学习如何使用此 HSM 签名数据。这通常是最棘手的部分,因为所有加密提供程序都不同。你应该遵循 USB 令牌或云 API 的文档。

理想情况下,你需要找到相应的 .NET SDK。或者至少找到本机 SDK,并通过 .NET 中的互操作来使用它。

你的目标是获得能够使用证书对任意字节进行签名的 C# 或 VB.NET 代码。

用于外部签名的 PDF API

一旦你能够使用 HSM 设备对任意字节签名,就可以使用 Docotic.Pdf 为 PDF 文档签名。为此,实现 IPdfSigner 接口 并将其用于签名。

实现 IPdfSigner 接口

IPdfSigner 声明两个成员。SignatureAlgorithm 属性返回用于签名的算法。通常,你会使用 RSA 或 ECDSA 算法。

Sign 方法是外部签名流程的关键部分。Docotic.Pdf 会将 PDF 文档字节的摘要传递给此方法。你应该使用 HSM 设备对该摘要签名,并返回数字签名的字节。注意,这里不能返回 PKCS#7 对象。只需生成并返回加密签名。

Docotic.Pdf 会为每个签名调用两次 Sign 方法。第一次调用是为了计算签名所需空间。第二次调用才是真正对文档进行签名。

为外部签名创建 PdfSigningOptions

使用 PdfSigningOptions(IPdfSigner, X509Certificate2[]) 构造函数 来使用你的 IPdfSigner 实现签名 PDF 文档。获取证书链并将其作为第二个参数提供。至少需要一个证书,并且签名证书应排在首位。

拼图的最后一块是 PdfSigningOptions.DigestAlgorithm 属性。该属性的值必须与 IPdfSigner.SignatureAlgorithm 以及 IPdfSigner.Sign 的实现保持一致。

让我们看看如何在常见场景下使用外部签名对 PDF 签名。

在 C# 中使用 PKCS#11 驱动程序签名 PDF 文档

如果你的加密 USB 令牌或智能卡附带 PKCS#11 驱动程序,那么你可以在 .NET 中使用 Pkcs11Interop.X509StorePkcs11Interop 库。Pkcs11Interop 与 Atos CardOS 智能卡、YubiKey PIV、SmartCard-HSM、SafeNet ProtectServer HSM 以及其他 HSM 兼容。

使用 PKCS#11 驱动程序签署 PDF 文档 代码示例展示了如何使用 USB 令牌或智能卡为 PDF 文档签名。你需要自定义以下几行:

string pkcsLibraryPath = @"C:\Program Files\SoftHSM2\lib\softhsm2-x64.dll";
ulong slotId = 1743347971;
string alias = "YOUR-ALIAS";
string certLabel = "YOUR-CERTIFICATE";
string pin = "YOUR-PIN";
var digestAlgorithm = PdfDigestAlgorithm.Sha512;

我们的示例配置使用 SoftHSM2 作为 USB 令牌模拟器。在实际应用中,你不需要 SoftHSM2。相反,请使用 USB 令牌附带的驱动程序。

使用 Azure Key Vault 签名 PDF 文档

Microsoft Azure Key Vault 安全存储证书和密钥。你需要完成以下准备工作:

  1. 创建 Azure 帐户。
  2. 创建 Azure 用户。
  3. 创建一个 Key Vault。
  4. 为所选 Azure 用户授予对 Key Vault 的访问权限。
  5. 向 Key Vault 添加文档签名证书。你可以从集成证书颁发机构 导入 证书。

然后,使用 Azure.Security.KeyVault.Keys NuGet 包在 .NET 中进行签名。使用 Azure Key Vault 中密钥进行签名的 C# 示例代码:

var vaultUrl = new Uri("https://YOUR-VAULT.vault.azure.net/");
var credentials = new DefaultAzureCredential();

var keyClient = new KeyClient(vaultUrl, credentials);
KeyVaultKey key = keyClient.GetKey("YOUR-KEY");
var cryptoClient = new CryptographyClient(key.Id, credentials);
byte[] messageToSign = ..;
byte[] signature = cryptoClient.SignData(SignatureAlgorithm.RS512, messageToSign).Signature;

相关的 IPdfSigner 实现可能如下所示:

using AzureSignatureAlgorithm = SignatureAlgorithm;

class AzureSigner : IPdfSigner
{
    private readonly CryptographyClient m_client;
    private readonly AzureSignatureAlgorithm m_signingAlgorithm;

    public AzureSigner(CryptographyClient client, AzureSignatureAlgorithm signingAlgorithm)
    {
        m_client = client;
        m_signingAlgorithm = signingAlgorithm;
    }

    public PdfSignatureAlgorithm SignatureAlgorithm
    {
        get
        {
            if (m_signingAlgorithm == AzureSignatureAlgorithm.RS256 ||
                m_signingAlgorithm == AzureSignatureAlgorithm.RS384 ||
                m_signingAlgorithm == AzureSignatureAlgorithm.RS512)
                return PdfSignatureAlgorithm.Rsa;

            throw new NotSupportedException($"Unsupported {nameof(SignatureAlgorithm)} value: {m_signingAlgorithm}");
        }
    }

    public PdfDigestAlgorithm DigestAlgorithm
    {
        get
        {
            if (m_signingAlgorithm == AzureSignatureAlgorithm.RS256)
                return PdfDigestAlgorithm.Sha256;

            if (m_signingAlgorithm == AzureSignatureAlgorithm.RS384)
                return PdfDigestAlgorithm.Sha384;

            if (m_signingAlgorithm == AzureSignatureAlgorithm.RS512)
                return PdfDigestAlgorithm.Sha512;

            throw new NotSupportedException($"Unsupported {nameof(SignatureAlgorithm)} value: {m_signingAlgorithm}");
        }
    }

    public byte[] Sign(byte[] message)
    {
        return m_client.SignData(m_signingAlgorithm, message).Signature;
    }
}

最后,使用此 AzureSigner 类为 PDF 文档签名:

CryptographyClient cryptoClient = ..;
using X509Certificate2 cert = ..;
var signer = new AzureSigner(cryptoClient, SignatureAlgorithm.RS512);

using var pdf = new PdfDocument();

PdfPage page = pdf.Pages[0];
PdfSignatureField field = page.AddSignatureField(50, 50, 200, 200);

var options = new PdfSigningOptions(signer, [cert])
{
    DigestAlgorithm = signer.DigestAlgorithm,
    Field = field,
};

pdf.SignAndSave(options, ..);

在 GitHub 上查看完整的 使用 Azure Key Vault 签署 PDF 文档 代码示例。

使用 AWS KMS 签名 PDF 文档

AWS 密钥管理服务 (KMS) 是另一种用于加密密钥的安全存储。准备步骤与 Azure Key Vault 相同:

  1. 创建 AWS 帐户。
  2. 创建 IAM 用户。
  3. 添加签名密钥。
  4. 为所选 IAM 用户授予对密钥的访问权限。

对于 AWS,你需要在 .NET 中使用 AWSSDK.KeyManagementService NuGet 包。PDF 签名示例代码与 Azure 场景非常接近:

using var pdf = new PdfDocument();

PdfPage page = pdf.Pages[0];
PdfSignatureField field = page.AddSignatureField(50, 50, 200, 200);

var signingAlgorithm = SigningAlgorithmSpec.RSASSA_PSS_SHA_512;
var signer = new AwsSigner("arn:aws:kms:YOUR-id", signingAlgorithm);
var options = new PdfSigningOptions(signer, [cert])
{
    DigestAlgorithm = signer.DigestAlgorithm,
    Field = field,
};

pdf.SignAndSave(options, ..);


class AwsSigner : IPdfSigner
{
    private readonly string m_keyId;
    private readonly SigningAlgorithmSpec m_signingAlgorithm;

    public AwsSigner(string keyId, SigningAlgorithmSpec signingAlgorithm)
    {
        m_keyId = keyId;
        m_signingAlgorithm = signingAlgorithm;
    }

    public PdfSignatureAlgorithm SignatureAlgorithm
    {
        get
        {
            if (m_signingAlgorithm == SigningAlgorithmSpec.RSASSA_PKCS1_V1_5_SHA_256 ||
                m_signingAlgorithm == SigningAlgorithmSpec.RSASSA_PKCS1_V1_5_SHA_384 ||
                m_signingAlgorithm == SigningAlgorithmSpec.RSASSA_PKCS1_V1_5_SHA_512)
                return PdfSignatureAlgorithm.Rsa;

            throw new NotSupportedException($"Unsupported {nameof(SigningAlgorithmSpec)} value: {m_signingAlgorithm}");
        }
    }

    public PdfDigestAlgorithm DigestAlgorithm
    {
        get
        {
            string alg = m_signingAlgorithm.Value;
            if (alg.EndsWith("256"))
                return PdfDigestAlgorithm.Sha256;

            if (alg.EndsWith("384"))
                return PdfDigestAlgorithm.Sha384;

            if (alg.EndsWith("512"))
                return PdfDigestAlgorithm.Sha512;

            throw new NotSupportedException($"Unsupported {nameof(SigningAlgorithmSpec)} value: {m_signingAlgorithm}");
        }
    }

    public byte[] Sign(byte[] message)
    {
        using var kmsClient = new AmazonKeyManagementServiceClient();
        using var stream = new MemoryStream(message);
        var signRequest = new SignRequest()
        {
            SigningAlgorithm = m_signingAlgorithm,
            KeyId = m_keyId,
            MessageType = MessageType.RAW,
            Message = stream,
        };
        SignResponse signResponse = kmsClient.SignAsync(signRequest).GetAwaiter().GetResult();
        return signResponse.Signature.ToArray();
    }
}

完整的 使用 AWS KMS 签署 PDF 文档 代码示例也可在 GitHub 上获取。

结论

利用 Docotic.Pdf 库 使用 USB 令牌、智能卡或基于云的 HSM 为 PDF 文档签名。为此,请在 C# 或 VB.NET 代码中实现 IPdfSigner 接口。

从 GitHub 下载并试用 PDF 数字签名代码示例。查看相关文章以获取更多信息:

常见问题

如何使用 USB 令牌或智能卡签名 PDF?

使用 Docotic.Pdf 和 Pkcs11Interop .NET 库通过令牌或智能卡为 PDF 签名。有关更多细节,请参阅 在 C# 中使用 PKCS#11 驱动程序签名 PDF 文档 部分。

如何将 SoftHSM 与我的 USB 令牌或智能卡一起使用?

SoftHSM 模拟真实的 HSM 设备。如果你有 USB 令牌或智能卡,就不需要 SoftHSM。相反,请使用 HSM 设备随附的 PKCS#11 驱动程序。

如何使用外部签名为 PDF 文档签名?

查看 使用 Azure Key Vault 签名 PDF 文档使用 AWS KMS 签名 PDF 文档 部分。使用其他基于云的 HSM 进行外部 PDF 签名遵循类似流程。

如何在 .NET 中使用 Azure Key Vault?

使用 Azure.Security.KeyVault.Keys NuGet 包。试试 GitHub 上的 使用 Azure Key Vault 签署 PDF 文档 代码示例。