C#를 사용하여 PDF 문서에 디지털 서명 추가

발행: (2025년 12월 24일 오후 12:14 GMT+9)
12 min read
원문: Dev.to

Source: Dev.to

디지털 서명은 최신 문서 준수 및 데이터 보안을 위해 필수적입니다.
엔터프라이즈 수준의 .NET 개발에서는 계약 관리, 공식 문서 발행, 기밀 파일 배포와 같은 시나리오에 대해 **C#**를 사용한 PDF 디지털 서명 기능 구현이 높은 우선순위 요구사항입니다.

Free Spire.PDF for .NET은 가볍고 무료인 PDF 처리 라이브러리로, Adobe Acrobat과 같은 서드파티 소프트웨어 없이도 PDF 디지털 서명을 원활하게 통합할 수 있게 해줍니다.

1️⃣ 설치

Install-Package FreeSpire.PDF

NuGet 명령은 종속성을 자동으로 해결하고 라이브러리를 .NET 프로젝트에 통합합니다.

2️⃣ 인증서 요구 사항

환경.pfx 인증서를 얻는 방법
테스트OpenSSL 또는 Windows 인증서 관리자 를 사용하여 자체 서명 인증서를 생성합니다 (기능 테스트에만 적합).
프로덕션현지 규정을 준수하는 신뢰할 수 있는 인증 기관(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}");
            }
        }
    }
}

Note: 보이지 않는 서명은 최종 사용자에게 시각적인 확인을 제공하지 않습니다.

5️⃣ 고급 가시 서명 (맞춤형)

아래 예제는 텍스트, 이미지 및 레이아웃을 사용자 정의할 수 있는 가시 서명 필드를 추가합니다—계약서 및 공식 문서에 이상적입니다.

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️⃣ 요약 및 팁

주제
인증서 처리서버 환경에서는 MachineKeySet + EphemeralKeySet 사용을 권장합니다; 운영 환경에서는 절대로 비밀번호를 하드코딩하지 마세요.
오류 처리CryptographicException, IOException, 그리고 일반 Exception을 잡아 명확한 진단 정보를 제공하세요.
파일 경로상대 경로나 설정 파일을 사용하여 환경 간 이식성을 향상시키세요.
가시 서명서명 사각형을 조정하고, 텍스트(PdfSignatureAppearance)를 추가하며, 이미지를 삽입하여 전문적인 모습을 구현하세요.
테스트Adobe Acrobat Reader 또는 디지털 서명을 지원하는 PDF 뷰어로 서명을 검증하세요.

7️⃣ 다음 단계

  1. Complete the visible‑signature demo – 사각형 위치 지정, 외관 설정 및 이미지 로딩을 추가합니다.
  2. Integrate with your CI/CD pipeline – 배포 전에 생성된 PDF에 서명을 자동화합니다.
  3. Implement verification logicPdfSignatureValidator(또는 서드‑파티 검증기)를 사용하여 수신 측에서 서명 무결성을 확인합니다.

Happy signing! 🚀

코드 예제 – 보이는 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보이는 서명 레이아웃을 정의합니다옵션: SignImageOnly, SignDetailOnly, SignImageAndSignDetail
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?

  • 경량 및 손쉬운 통합 API – 무거운 서드파티 종속성이 없습니다.
  • PdfOrdinarySignatureMaker를 통한 보이지 않는 서명 및 보이는 서명에 대한 완전 지원.
  • 포괄적인 문서 및 예제 – 보안, 권한, 배포 등 일반적인 기업 문제점을 해결합니다.

문서 관리 시스템, 계약 서명 플랫폼, 혹은 기밀 파일 배포 도구를 구축하든, 이 접근 방식은 비용 효율적이며 고성능, 프로덕션 준비가 된 PDF 디지털 서명 솔루션을 제공합니다.

Back to Blog

관련 글

더 보기 »

.NET에서 NuGet 패키지 사용 마스터하기

NuGet은 실제로 무엇인가요? NuGet을 .NET의 Amazon이나 Mercado Libre라고 생각해 보세요. 가구의 모든 나사를 직접 만들지 않고, 가게에 주문하듯이요. - 패키지…