소개 및 동기
엔터프라이즈급 시스템에 디지털 서명을 구현할 때 보안은 절대 양보할 수 없습니다.
로컬 PFX 또는 P12 파일에 인증서를 저장하는 것은 편리하지만 개인 키가 추출되거나 손상될 위험이 있습니다. 반면에 PKCS#11 하드웨어 토큰(USB 동글, 스마트 카드, HSM 등)은 키를 변조 방지 경계 안에 보관하여 절대로 장치를 떠나지 않도록 합니다.
이 게시물에서는 GroupDocs.Signature for .NET과 Pkcs11Interop을 사용해 하드웨어 토큰으로 PDF 문서에 서명하는 방법을 보여줍니다. 이 접근 방식은 편리함과 규정 준수를 결합합니다: GroupDocs는 모든 PDF 수준 패키징(서명 필드, 다이제스트 계산, 임베딩)을 처리하고, 토큰은 실제 암호화 서명을 수행합니다.
⚠️ 초기 구현 안내
이 솔루션은 현재 PKCS#11 디지털 서명 동글을 GroupDocs.Signature와 함께 사용하기 위한 초기 구현으로 제공됩니다.
하드웨어 토큰을 사용한 문서 서명을 가능하게 하지만, 귀하의 환경에서 추가 테스트를 수행하여 규정 준수 및 보안 요구 사항을 충족하는지 확인할 것을 강력히 권장합니다.
여러분의 피드백, 테스트 결과 및 개선 제안을 매우 환영합니다.
도전 과제: PKCS#11과 PDF 서명 연결
PKCS#11 토큰을 문서 서명 워크플로에 통합하는 데는 여러 비정형적인 어려움이 있습니다:
- 저수준 복잡성 — PKCS#11 API(Cryptoki)는 올바른 개인 키를 찾기 위해 슬롯, 세션, 핸들 및 속성을 관리해야 합니다.
- PDF 수준 패키징 — PDF 서명은 단순히 바이트에 서명하는 것이 아니라, 선택된 바이트 범위에 대한 정확한 다이제스트를 계산하고, 서명을 CMS/PKCS#7 컨테이너에 래핑하며, 타임스탬프를 포함하고, 검증 정보를 임베드해야 합니다.
- 벤더 차이 — 토큰/벤더 모듈마다 맞춤형 속성 매핑이나 추가 미들웨어가 필요할 수 있습니다.
- 규정 준수 및 감사 가능성 — 운영 시스템은 견고한 PIN 처리, 세션 수명 주기 제어, 오류 복구 및 로깅이 필요합니다.
이 샘플 프로젝트는 GroupDocs.Signature의 ICustomSignHash 인터페이스와 Pkcs11Interop을 결합해 서명을 토큰에 오프로드하고, PDF 구조는 GroupDocs가 담당하도록 함으로써 위 문제들을 해결합니다.
샘플 프로젝트가 수행하는 작업
- PKCS#11 토큰(동글, 스마트 카드, HSM)을 사용한 PDF 문서 서명을 시연합니다.
- Windows 인증서 저장소 폴백을 지원합니다: Windows에 인증서가 설치된 경우 코드는 이를 대신 사용할 수 있습니다.
- 맞춤형 해시 서명을 구현합니다: GroupDocs가 다이제스트를 계산하고, 토큰은 해시만 서명합니다.
- 개인 키는 하드웨어에 항상 보관됩니다 — 절대로 내보내지 않습니다.
- 토큰 로직(세션, 키 조회, 서명)을
Pkcs11DigitalSigner.cs에 캡슐화합니다. Helpers.cs에 보조 로직을 제공합니다(예: Windows 저장소에서 인증서 조회).- 설정은
Settings.cs에 중앙 집중화되었습니다. - 환경에 맞게 조정할 수 있는 레퍼런스 구현으로 작동합니다.
설정 및 전제 조건
전제 조건
- .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.cs —
Pkcs11LibraryPath,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);
// }
}
}
핵심 포인트:
DigitalSignOptions의CustomSignHash속성을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 서비스 또는 클라우드 기반 토큰 저장소와의 통합