소개 및 동기

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

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

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

도전 과제: 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에 집중
  • 환경에 맞게 조정할 수 있는 레퍼런스 구현 제공
PKCS#11과 PDF 서명의 연계

설정 및 전제 조건

전제 조건

  • .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 연산은 스레드 안전하지 않을 수 있습니다. 벤더가 다중 스레드를 지원하지 않으면 single‑threaded 컨텍스트를 사용하세요.
  • 시간 초과·세션 리셋 → 장시간 연산 시 세션이 종료되거나 타임아웃될 수 있으니 세션 관리와 정리 코드를 적절히 구현하세요.

보안 및 권장 사항

  • 운영 환경에서는 PIN·라이브러리 경로와 같은 비밀 정보를 하드코딩하지 말고 보안 구성 또는 비밀 관리 도구를 사용하세요.
  • 강력한 PIN을 사용하고 정책이 허용하는 경우 주기적으로 교체하세요.
  • 민감한 PIN을 로그에 남기지 않으며, 작업·오류를 로깅하세요.
  • 서명 후 즉시 토큰 세션을 종료하고 로그아웃하세요.
  • 서명 검증(인증서 체인, 타임스탬프) 작업을 수행하세요.
  • 다양한 환경·토큰 종류(동글·스마트 카드·HSM)에서 충분히 테스트하세요.

다음 단계 및 추가 자료

직접 실행해 보고 싶으신가요? 저장소를 복제하고 플레이스홀더를 업데이트한 뒤 샘플을 실행해 보세요. 다음과 같은 주제를 살펴볼 수 있습니다:

  • 사용자 정의 해시 서명(다이제스트와 서명을 토큰에 위임)
  • 타임스탬프 및 LTV/DSS 삽입
  • 반복 서명(하나의 문서에 다중 서명)
  • 원격 HSM 서비스 또는 클라우드 기반 토큰 저장소와의 연계

외부 링크