소개 및 동기

엔터프라이즈급 시스템에 디지털 서명을 구현할 때 보안은 절대 양보할 수 없습니다.
로컬 PFX 또는 P12 파일에 인증서를 저장하는 것은 편리하지만 개인 키가 추출되거나 손상될 위험이 있습니다. 반면에 PKCS#11 하드웨어 토큰(USB 동글, 스마트 카드, HSM 등)은 키를 변조 방지 경계 안에 보관하여 절대로 장치를 떠나지 않도록 합니다.

이 게시물에서는 GroupDocs.Signature for .NETPkcs11Interop을 사용해 하드웨어 토큰으로 PDF 문서에 서명하는 방법을 보여줍니다. 이 접근 방식은 편리함과 규정 준수를 결합합니다: GroupDocs는 모든 PDF 수준 패키징(서명 필드, 다이제스트 계산, 임베딩)을 처리하고, 토큰은 실제 암호화 서명을 수행합니다.

⚠️ 초기 구현 안내
이 솔루션은 현재 PKCS#11 디지털 서명 동글을 GroupDocs.Signature와 함께 사용하기 위한 초기 구현으로 제공됩니다.
하드웨어 토큰을 사용한 문서 서명을 가능하게 하지만, 귀하의 환경에서 추가 테스트를 수행하여 규정 준수 및 보안 요구 사항을 충족하는지 확인할 것을 강력히 권장합니다.
여러분의 피드백, 테스트 결과 및 개선 제안을 매우 환영합니다.

도전 과제: PKCS#11과 PDF 서명 연결

PKCS#11 토큰을 문서 서명 워크플로에 통합하는 데는 여러 비정형적인 어려움이 있습니다:

  1. 저수준 복잡성 — PKCS#11 API(Cryptoki)는 올바른 개인 키를 찾기 위해 슬롯, 세션, 핸들 및 속성을 관리해야 합니다.
  2. PDF 수준 패키징 — PDF 서명은 단순히 바이트에 서명하는 것이 아니라, 선택된 바이트 범위에 대한 정확한 다이제스트를 계산하고, 서명을 CMS/PKCS#7 컨테이너에 래핑하며, 타임스탬프를 포함하고, 검증 정보를 임베드해야 합니다.
  3. 벤더 차이 — 토큰/벤더 모듈마다 맞춤형 속성 매핑이나 추가 미들웨어가 필요할 수 있습니다.
  4. 규정 준수 및 감사 가능성 — 운영 시스템은 견고한 PIN 처리, 세션 수명 주기 제어, 오류 복구 및 로깅이 필요합니다.

이 샘플 프로젝트는 GroupDocs.SignatureICustomSignHash 인터페이스와 Pkcs11Interop을 결합해 서명을 토큰에 오프로드하고, PDF 구조는 GroupDocs가 담당하도록 함으로써 위 문제들을 해결합니다.

샘플 프로젝트가 수행하는 작업

  • PKCS#11 토큰(동글, 스마트 카드, HSM)을 사용한 PDF 문서 서명을 시연합니다.
  • Windows 인증서 저장소 폴백을 지원합니다: Windows에 인증서가 설치된 경우 코드는 이를 대신 사용할 수 있습니다.
  • 맞춤형 해시 서명을 구현합니다: GroupDocs가 다이제스트를 계산하고, 토큰은 해시만 서명합니다.
  • 개인 키는 하드웨어에 항상 보관됩니다 — 절대로 내보내지 않습니다.
  • 토큰 로직(세션, 키 조회, 서명)을 Pkcs11DigitalSigner.cs에 캡슐화합니다.
  • Helpers.cs에 보조 로직을 제공합니다(예: Windows 저장소에서 인증서 조회).
  • 설정은 Settings.cs에 중앙 집중화되었습니다.
  • 환경에 맞게 조정할 수 있는 레퍼런스 구현으로 작동합니다.
Bridging PKCS#11 with PDF Signing

설정 및 전제 조건

전제 조건

  • .NET 6.0 이상(또는 .NET Framework 4.6.2)
  • 토큰 벤더가 제공하는 유효한 PKCS#11 라이브러리(DLL)
  • 유효한 인증서가 포함된 하드웨어 토큰(USB 동글, 스마트 카드 또는 HSM)
  • GroupDocs.Signature for .NET(체험판 또는 라이선스)
  • Pkcs11Interop 라이브러리

설치

git clone https://github.com/groupdocs-signature/esign-documents-with-pkcs11-using-groupdocs-signature-dotnet.git
cd esign-documents-with-pkcs11-using-groupdocs-signature-dotnet
dotnet restore

Visual Studio 또는 선호하는 IDE에서 솔루션을 열고 종속성이 해결되었는지 확인합니다.

저장소 구조 상세 분석

GroupDocs.Signature-for-.NET-PKCS11-Sample/
├── GroupDocs.Signature-for-.NET-PKCS11-Sample.csproj      # 프로젝트 파일
├── Program.cs                                             # 진입점 및 사용 흐름
├── Settings.cs                                            # PKCS#11 / 토큰 구성
├── Helpers.cs                                             # 유틸리티 함수(Windows 저장소, 인증서 필터링)
├── Pkcs11DigitalSigner.cs                                 # PKCS#11을 통한 ICustomSignHash 구현
└── README.md                                              # 개요 및 사용 지침
  • Program.cs — 서명 흐름을 조정하고 토큰 기반 및 Windows 인증서 흐름을 모두 시연합니다.
  • Settings.csPkcs11LibraryPath, TokenPin, CertificateSubject에 대한 상수/플레이스홀더를 포함합니다.
  • Helpers.cs — Windows 저장소에서 주제 이름으로 인증서를 찾는 코드를 포함합니다(폴백 흐름에 사용).
  • Pkcs11DigitalSigner.cs — 핵심 로직: PKCS#11 모듈 로드, 세션 열기, 개인 키 객체 찾기, 다이제스트 서명, 그리고 X509Certificate2 또는 서명 콜백 구현 반환.
  • README.md — 개요, 도전 과제 및 사용 지침을 제공하며(이 블로그가 보완합니다).

코드 설명 및 흐름 안내

Settings.cs

public static class Settings
{
    public const string Pkcs11LibraryPath = "<PKCS11_LIBRARY_PATH>";
    public const string TokenPin = "<TOKEN_PIN>";
    public const string CertificateSubject = "<CERT_SUBJECT>";
}

배포 환경에 맞게 쉽게 교체할 수 있도록 구성 세부 정보를 분리했습니다.

Pkcs11DigitalSigner.cs — 고수준 흐름

public class Pkcs11DigitalSigner : ICustomSignHash
{
    public byte[] SignHash(byte[] hash)
    {
        // This method is invoked by GroupDocs.Signature when it needs the token to sign a hash
        using (var pkcs11 = new Pkcs11(Settings.Pkcs11LibraryPath, AppType.SingleThreaded))
        {
            // Load module, open session, login with PIN, find key and perform signing
        }
    }

    public X509Certificate2 GetCertificateFromPkcs11()
    {
        // Retrieves the public certificate from the token so the signing options can be configured
    }
}
  • SignHash는 중앙 메서드로, GroupDocs가 계산한 다이제스트를 받아 PKCS#11 API를 사용해 서명합니다.
  • GetCertificateFromPkcs11은 토큰에 저장된 공개 인증서를 가져와 서명 옵션에 올바른 메타데이터를 제공하도록 합니다.

Program.cs — 사용 흐름

class Program
{
    static void Main()
    {
        string inputFile = "sample.pdf";
        string outputFile = "signed.pdf";

        // (1) PKCS#11 signing
        var tokenSigner = new Pkcs11DigitalSigner();
        var cert = tokenSigner.GetCertificateFromPkcs11();

        using (var signature = new Signature(inputFile))
        {
            var options = new DigitalSignOptions(cert)
            {
                Comments = "Signed with PKCS#11 token",
                SignTime = DateTime.Now,
                CustomSignHash = tokenSigner  // link token-based signing
            };
            signature.Sign(outputFile, options);
        }

        // (2) Windows certificate store fallback (optional)
        // var storeCert = Helpers.GetCertificateFromWindowsStore(Settings.CertificateSubject);
        // using (var signature2 = new Signature(inputFile))
        // {
        //     var options2 = new DigitalSignOptions(storeCert) { ... };
        //     signature2.Sign("signed_store.pdf", options2);
        // }
    }
}

핵심 포인트:

  • DigitalSignOptionsCustomSignHash 속성을 tokenSigner로 설정해 GroupDocs가 실제 해시 서명을 토큰에 위임하도록 합니다.
  • 주석 처리된 폴백 흐름은 하드웨어 토큰을 사용할 수 없을 때 Windows 저장소 인증서로 전환하는 방법을 보여줍니다.

활용 사례 및 실제 시나리오

  • 인도 및 CA 발급 USB 서명 동글
    인도에서는 많은 법적 구속력이 있는 eSignature가 인증 기관이 발급한 USB 동글에 저장된 인증서를 요구합니다. 이 샘플은 문서 게이트웨이, 포털 등과 같은 애플리케이션이 해당 동글과 직접 통합할 수 있게 해줍니다.
  • 엔터프라이즈 문서 워크플로
    계약 관리나 승인 흐름 같은 내부 시스템에서 하드웨어 서명을 사용하면 무단 사용자가 문서 서명을 위조할 수 없습니다.
  • 법률/규정 기반 서명
    정부 및 규제 산업에서는 서명이 하드웨어 제어 키에서 나와야 한다는 요구가 많습니다. 이 통합은 엄격한 감사 및 규정 준수 요구를 충족하는 데 도움이 됩니다.

흔히 발생하는 문제 및 해결 방법

  • 잘못된 라이브러리 경로 → PKCS#11 DLL 경로가 토큰 벤더의 모듈(softhsm2.dll, cryptoki.dll 등)과 일치해야 합니다.
  • PIN 잠금 또는 실패 → PIN을 여러 번 잘못 입력하면 토큰이 잠길 수 있으니 벤더 정책을 확인하세요.
  • 키 미검색 → 정확한 인증서 주제가 지정되었는지 확인하고, 토큰에 해당 주제를 가진 인증서가 존재하는지 검증하세요.
  • 드라이버 또는 미들웨어 누락 → 일부 토큰은 Pkcs11Interop이 통신하기 전에 벤더 드라이버 설치가 필요합니다.
  • 스레딩 문제 → PKCS#11 작업은 스레드 안전하지 않을 수 있으니, 벤더가 다중 스레드를 지원하지 않는 한 단일 스레드 컨텍스트를 사용하세요.
  • 시간 초과 또는 세션 재설정 → 장시간 작업 시 세션이 닫히거나 시간 초과될 수 있으니 적절한 세션 관리와 정리 코드를 구현하세요.

보안 및 모범 사례

  • 운영 비밀(PIN, 라이브러리 경로 등)을 하드코딩하지 말고 보안 구성 또는 비밀 관리 도구를 사용하세요.
  • 강력한 PIN을 사용하고 정책이 허용하는 경우 주기적으로 교체하세요.
  • 민감한 PIN을 로그에 남기지 않으면서 작업 및 오류를 기록하세요.
  • 토큰 세션을 최소화하고 서명 후 즉시 로그아웃하세요.
  • 서명 후 체인 검증, 타임스탬프 검증 등으로 서명을 검증하세요.
  • 다양한 환경 및 토큰 유형(동글/스마트 카드/HSM)에서 테스트하세요.

다음 단계 및 리소스

레포를 복제하고 플레이스홀더를 업데이트한 뒤 샘플을 실행해 보세요. 다음 주제를 탐색해 볼 수 있습니다:

  • 맞춤형 해시 서명(다이제스트 + 서명을 토큰에 위임)
  • 타임스탬프 및 LTV/DSS 임베딩
  • 반복 서명(하나의 문서에 다중 서명)
  • 원격 HSM 서비스 또는 클라우드 기반 토큰 저장소와의 통합

외부 링크