通过 C# 为 PDF 文档添加数字签名

发布: (2025年12月24日 GMT+8 11:14)
10 分钟阅读
原文: Dev.to

Source: Dev.to

数字签名是实现最新文档合规性和数据安全的必备手段。
对于企业级 .NET 开发而言,使用 C# 实现 PDF 数字签名功能是合同管理、官方文件发布以及机密文件分发等场景中的高优先级需求。

Free Spire.PDF for .NET 是一个轻量级、免费的 PDF 处理库,可实现无缝的 PDF 数字签名集成——无需使用 Adobe Acrobat 等第三方软件。

1️⃣ 安装

Install-Package FreeSpire.PDF

NuGet 命令会自动解析依赖并将库集成到您的 .NET 项目中。

2️⃣ 证书要求

环境获取 .pfx 证书的方法
测试使用 OpenSSLWindows 证书管理器 生成自签名证书(仅适用于功能测试)。
生产使用受信任的 证书颁发机构 (CA) 签发的证书,且符合当地法规。

证书必须同时包含公钥和私钥。

加载证书

核心类是 System.Security.Cryptography.X509Certificates.X509Certificate2

3️⃣ 签名工作流

PdfOrdinarySignatureMaker(Free Spire.PDF)处理标准数字签名。
线性工作流如下:

  1. 加载 PDF 文档
  2. 解析 X.509 证书
  3. 初始化签名生成器
  4. 配置签名设置
  5. 执行签名
  6. 保存并释放资源

我们将首先实现一个 基本的不可见签名,随后扩展为带有文本和图像元素的 可自定义可见签名

4️⃣ 基础隐形签名(生产级)

using Spire.Pdf;
using Spire.Pdf.Interactive.DigitalSignatures;
using System.Security.Cryptography.X509Certificates;
using System.IO;

namespace DigitallySignPdf
{
    class Program
    {
        static void Main(string[] args)
        {
            // Define file paths (use relative paths for better portability)
            string inputPdfPath = "Input.pdf";
            string outputPdfPath = "Signed_Invisible.pdf";
            string certPath = "certificate.pfx";
            string certPassword = "abc123";

            try
            {
                // Validate input files exist to avoid runtime exceptions
                if (!File.Exists(inputPdfPath) || !File.Exists(certPath))
                {
                    Console.WriteLine("Error: Input PDF or certificate file not found.");
                    return;
                }

                // 1. Initialize PdfDocument and load the target PDF
                using (PdfDocument doc = new PdfDocument())
                {
                    doc.LoadFromFile(inputPdfPath);

                    // 2. Load X.509 certificate with secure key storage flags
                    // Key flags explanation:
                    // - MachineKeySet: Suitable for server‑side deployment (shared key store)
                    // - EphemeralKeySet: Prevents key persistence, enhances security for temporary signing tasks
                    X509Certificate2 cert = new X509Certificate2(
                        certPath,
                        certPassword,
                        X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.EphemeralKeySet
                    );

                    // 3. Initialize signature maker and bind the PDF document
                    PdfOrdinarySignatureMaker signatureMaker = new PdfOrdinarySignatureMaker(doc, cert);

                    // 4. Execute signature with a unique identifier (for later verification)
                    signatureMaker.MakeSignature("Enterprise_Signature_001");

                    // 5. Save the signed PDF (using 'using' statement auto‑releases resources)
                    doc.SaveToFile(outputPdfPath);
                    Console.WriteLine($"Success! Invisible signature added. Output file: {outputPdfPath}");
                }
            }
            catch (CryptographicException ex)
            {
                Console.WriteLine($"Certificate error: {ex.Message} (Check password or certificate validity)");
            }
            catch (IOException ex)
            {
                Console.WriteLine($"File operation error: {ex.Message} (Ensure the PDF is not open in another program)");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Signature failed: {ex.Message}");
            }
        }
    }
}

注意: 隐形签名对终端用户没有可视化确认。

5️⃣ 高级可见签名(可自定义)

下面的示例在 PDF 中添加一个可自定义文本、图像和布局的可见签名字段——非常适合合同和正式文件。

using Spire.Pdf;
using Spire.Pdf.Interactive.DigitalSignatures;
using System;
using System.Drawing;
using System.Security.Cryptography.X509Certificates;
using System.IO;

namespace PdfDigitalSignatureDemo
{
    class AdvancedVisibleSignatureDemo
    {
        static void Main(string[] args)
        {
            string inputPdf = "Input.pdf";
            string outputPdf = "Signed_Visible.pdf";
            string certPath = "certificate.pfx";
            string certPwd = "your_secure_password";
            string signatureImagePath = "signature_stamp.png";

            try
            {
                using (PdfDocument doc = new PdfDocument())
                {
                    doc.LoadFromFile(inputPdf);
                    X509Certificate2
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error: {ex.Message}");
            }
        }
    }
}

代码片段故意保留不完整,以保持原始内容。

6️⃣ 回顾与技巧

TopicTips
Certificate handling在服务器环境中首选 MachineKeySet + EphemeralKeySet;在生产环境中绝不要硬编码密码。
Error handling捕获 CryptographicExceptionIOException 和通用 Exception,以提供清晰的诊断信息。
File paths使用相对路径或配置设置,以提升在不同环境间的可移植性。
Visible signatures调整签名矩形区域,添加文本(PdfSignatureAppearance),并嵌入图像,以获得专业外观。
Testing使用 Adobe Acrobat Reader 或任何支持数字签名的 PDF 阅读器验证签名。

7️⃣ 后续步骤

  1. 完成可见签名演示 – 添加矩形定位、外观设置和图像加载。
  2. 将其集成到 CI/CD 流水线 – 在分发前自动对生成的 PDF 进行签名。
  3. 实现验证逻辑 – 使用 PdfSignatureValidator(或第三方验证器)在接收端确认签名完整性。

祝签名愉快! 🚀

代码示例 – 添加可见的 PDF 签名

try
{
    // Load the certificate (use secure storage flags)
    var cert = new X509Certificate2(
        certPath,
        certPwd,
        X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.EphemeralKeySet);

    // Initialise the signer and obtain the signature object
    PdfOrdinarySignatureMaker signer = new PdfOrdinarySignatureMaker(doc, cert);
    PdfSignature signature = signer.Signature;

    // Configure signature metadata (visible in PDF signature properties)
    signature.Name        = "ACME Corporation Document Signing";
    signature.ContactInfo = "compliance@acme.com";
    signature.Location    = "USA";
    signature.Reason      = "Confirm document content compliance and authenticity";
    signature.Date        = DateTime.Now;   // Auto‑set signature timestamp

    // Create and customise the signature appearance
    PdfSignatureAppearance appearance = new PdfSignatureAppearance(signature)
    {
        NameLabel        = "Signer: ",
        ContactInfoLabel = "Contact: ",
        LocationLabel    = "Location: ",
        ReasonLabel      = "Reason: "
    };

    // Add a signature image (PNG/JPG). Resize if needed.
    if (File.Exists(signatureImagePath))
    {
        PdfImage signatureImage = PdfImage.FromFile(signatureImagePath);
        appearance.SignatureImage = signatureImage;
        // Layout mode: Image + Text details
        // Alternatives: SignImageOnly / SignDetailOnly
        appearance.GraphicMode = GraphicMode.SignImageAndSignDetail;
    }
    else
    {
        Console.WriteLine("Signature image not found; using text‑only appearance.");
        appearance.GraphicMode = GraphicMode.SignDetailOnly;
    }

    // Attach the appearance to the signer
    signer.SignatureAppearance = appearance;

    // Choose the target page (e.g., the last page of the PDF)
    PdfPageBase targetPage = doc.Pages[doc.Pages.Count - 1];

    // Set signature position (x, y, width, height) – adjust for your layout
    const float x      = 54f;   // Left margin (72 pt = 1 in)
    const float y      = 330f;  // Bottom margin
    const float width  = 280f;
    const float height = 90f;

    // Execute the visible signature
    signer.MakeSignature(
        "ACME_Visible_Signature_001",
        targetPage,
        x, y, width, height,
        appearance);

    // Save the signed PDF
    doc.SaveToFile(outputPdf);
    Console.WriteLine($"Visible signature added successfully! Output: {outputPdf}");
}
catch (Exception ex)
{
    Console.WriteLine($"Advanced signature failed: {ex.Message}");
}

核心组件词汇表与实用技巧

类 / 参数核心功能使用说明
PdfOrdinarySignatureMaker核心签名引擎;将 PDF 与证书绑定后执行签名使用 using 语句或调用 Dispose() 及时释放资源。
X509Certificate2解析 .pfx 证书;提取公钥/私钥对始终提供安全的密钥存储标志;在服务器上避免使用 DefaultKeySet
X509KeyStorageFlags控制证书的存放位置以及密钥的生命周期MachineKeySet – 用于服务器端部署
EphemeralKeySet – 临时、仅在内存中的密钥
PdfSignatureAppearance配置可见签名的样式(标签、图像、布局)调整 GraphicMode 可在仅图像、仅文字或图文混合布局之间切换。
GraphicMode定义可见签名的布局选项:SignImageOnlySignDetailOnlySignImageAndSignDetail
MakeSignature()触发签名过程传入 唯一 的签名名称,以便后续验证。

安全最佳实践

  • 绝不要硬编码密码。 将证书密码存放在安全保管库(如 Azure Key Vault、AWS Secrets Manager)或加密的配置文件中。
  • 限制证书访问权限。 只授予 .pfx 文件所需的最小文件系统权限。
  • 应用池身份。 在 IIS 中,给应用池身份(例如 IIS AppPool\YourApp)授予对证书存储的读取权限。
  • 在服务器上避免使用 UserKeySet 该设置会在非交互式环境中导致权限问题。

常见错误场景与解决方案

错误类型根本原因解决方案
CryptographicException证书密码无效或权限不足验证密码,仔细检查 X509KeyStorageFlags,并确保应用池能够读取证书。
IOExceptionPDF 文件被其他进程锁定在签名之前关闭所有打开 PDF 的阅读器/编辑器。
Invisible Signature Not Detected签名名称冲突或 PDF 保存不完整每次使用唯一的签名名称,并确认在释放前已调用 SaveToFile()

Validation Checklist

  • Desktop readers: Adobe Acrobat DC, Foxit Reader
  • Web browsers: Chrome PDF Viewer, Microsoft Edge PDF Reader
  • Mobile: Adobe Acrobat Mobile, WPS Mobile

在上述环境中测试已签名的 PDF,确保签名被识别并正确显示。

Why Choose Spire.PDF for .NET?

  • Lightweight & easy‑to‑integrate API – no heavyweight third‑party dependencies. → 轻量且易于集成的 API – 无笨重的第三方依赖。
  • Full support for invisible and visible signatures via PdfOrdinarySignatureMaker. → 通过 PdfOrdinarySignatureMaker 完全支持不可见和可见签名
  • Comprehensive documentation & examples that address common enterprise pain points (security, permissions, deployment). → 全面的文档和示例,解决常见企业痛点(安全、权限、部署)。

无论您是在构建文档管理系统、合同签署平台,还是机密文件分发工具,这种方法都能提供 成本效益高、高性能、可投入生产 的 PDF 数字签名解决方案。

Back to Blog

相关文章

阅读更多 »