Biz.Bizadm.KMS.Credentials 1.0.2

dotnet add package Biz.Bizadm.KMS.Credentials --version 1.0.2
                    
NuGet\Install-Package Biz.Bizadm.KMS.Credentials -Version 1.0.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Biz.Bizadm.KMS.Credentials" Version="1.0.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Biz.Bizadm.KMS.Credentials" Version="1.0.2" />
                    
Directory.Packages.props
<PackageReference Include="Biz.Bizadm.KMS.Credentials" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Biz.Bizadm.KMS.Credentials --version 1.0.2
                    
#r "nuget: Biz.Bizadm.KMS.Credentials, 1.0.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Biz.Bizadm.KMS.Credentials@1.0.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Biz.Bizadm.KMS.Credentials&version=1.0.2
                    
Install as a Cake Addin
#tool nuget:?package=Biz.Bizadm.KMS.Credentials&version=1.0.2
                    
Install as a Cake Tool

Biz.Bizadm.KMS

Key Management System 라이브러리. 공통 ICipher 아래에 KEK wrap/unwrap용 IKekCipher와 데이터 암·복호화용 IDekCipher를 두고, 소프트웨어·TPM·Azure Key Vault·PKCS#11 HSM 백엔드를 제공한다.

대상 프레임워크: net10.0

계층

ICipher
├── IKekCipher                       # 키 물질 wrap/unwrap (DEK 보호)
│   ├── AesGcmKekCipher              # 소프트웨어 AES-GCM
│   ├── TpmKekCipher                 # TPM AES-256-CFB / RSA-OAEP-256
│   ├── AzureKeyVaultKekCipher       # Azure Key Vault RSA-OAEP wrap
│   └── Pkcs11KekCipher              # PKCS#11 HSM RSA-OAEP wrap (별도 패키지)
├── IKekManager                      # KEK 버전 관리·DEK re-wrap
│   ├── AesGcmKekManager
│   ├── TpmKekManager
│   ├── AzureKeyVaultKekManager
│   └── Pkcs11KekManager
└── IDekCipher                       # 데이터 암·복호화
    └── AesGcmDekCipher              # AES-GCM (DEK는 envelope로 보관)

AesGcmKekCipherAesGcmDekCipherAbstractAesGcmCipher를 공유한다. 출력 형식은 nonce(12) || cipher || tag(16).

public interface ICipher : IDisposable
{
    byte[] Encrypt(byte[] plain);
    Task<byte[]> EncryptAsync(byte[] plain, CancellationToken cancellationToken = default);
    byte[] Decrypt(byte[] encrypted);
    Task<byte[]> DecryptAsync(byte[] encrypted, CancellationToken cancellationToken = default);
}

public interface IKekCipher : ICipher
{
    string KeyId { get; }
}
public interface IDekCipher : ICipher { }

IKekManager

KEK 버전 레지스트리와 DEK re-wrap 오케스트레이션을 담당한다. 호스트 앱은 개별 IKekCipher보다 Manager를 권장 진입점으로 사용한다.

Manager Rotate API LoadKey API
AesGcmKekManager Rotate(IKekCredentialProvider, byte[] newSalt, int iterationCount) LoadKey(IKekCredentialProvider, byte[] salt, int iterationCount)
AzureKeyVaultKekManager RotateAsync(CancellationToken) LoadKeyAsync(string version, CancellationToken)
TpmKekManager Rotate(FileInfo newKekBlobFile) LoadKey(FileInfo kekBlobFile)
Pkcs11KekManager Rotate(string newKeyLabel) LoadKey(string keyLabel)

공통 API: Current, Resolve(keyId), RewrapDek(envelope), RewrapDekFile(dekFile), Release(keyId).

로테이션 후 이전 KEK는 registry에 남아 아직 re-wrap되지 않은 DEK도 처리할 수 있다. 프로세스 재시작 등으로 registry가 비어 있으면 LoadKey로 envelope에 기록된 old KEK를 다시 올린다. LoadKeyCurrent를 바꾸지 않고 registry에만 등록한다. 모든 DEK re-wrap이 끝나면 Release(oldKeyId)로 이전 KEK를 해제한다. CurrentRelease할 수 없다.

동일 KeyIdRotate/LoadKey를 호출하면 InvalidOperationException이 발생하고, 등록에 실패한 cipher는 즉시 Dispose된다. RewrapDekAsync/RewrapDekFileAsync는 lock 밖에서 await하므로, 비동기 re-wrap 진행 중에는 Release/Dispose를 호출하지 않는다.

using AesGcmKekManager manager = AesGcmKekManager.Create(credentialProvider, salt, iterations);
using AesGcmDekCipher dek = AesGcmDekCipher.Create(manager.Current, new FileInfo("dek.bin"));

string oldKeyId = manager.Current.KeyId;
manager.Rotate(newCredential, newSalt, iterations);
manager.RewrapDekFile(new FileInfo("dek.bin"));
manager.Release(oldKeyId);

KEK Rotation과 DEK envelope

KEK rotation은 DEK 평문을 바꾸지 않고 wrap만 새 KEK로 교체한다. DEK rotation(데이터 재암호화)은 별도 작업이다.

IKekCipher.KeyId 형식:

구현 KeyId
AesGcmKekCipher aesgcm:{SHA256(derivedKey)}
AzureKeyVaultKekCipher azurekv:{keyName}:{version}
TpmKekCipher tpm:{SHA256(kekPublic)}
Pkcs11KekCipher pkcs11:{SHA256(modulus\|\|exponent)}

wrap된 DEK는 envelope 포맷(breaking change)으로 저장한다. raw ciphertext는 더 이상 읽지 않는다.

필드 크기 설명
Magic 4 KDEK
Version 1 1
KeyIdLength 2 UTF-8 KeyId 길이
KeyId variable wrap에 사용된 KEK KeyId
WrappedKeyLength 4 ciphertext 길이
WrappedKey variable KEK로 wrap된 DEK

저수준 re-wrap: targetKek.RewrapDek(sourceKek, wrappedDek) 또는 AesGcmDekCipher.Rewrap(source, target, dekFile).

로컬 AES·TPM·PKCS#11 구현의 *Async는 동기 경로를 Task.FromResult로 감싼다. Azure Key Vault 구현은 SDK 비동기 API를 그대로 사용한다.

KEK 생성에 쓰는 패스워드는 IKekCredentialProvider로 주입한다. CreateGetPassword()로 받은 뒤 사용 후 ZeroMemory한다.

public interface IKekCredentialProvider
{
    byte[] GetPassword();
    Task<byte[]> GetPasswordAsync(CancellationToken cancellationToken = default);
}
구현 저장소
AzureKeyVaultKekCredentialProvider Azure Key Vault Secret (없으면 32바이트 시크릿 생성)
OsKekCredentialProvider (Biz.Bizadm.KMS.Credentials) OS 자격 증명 금고 (RID별 runtime)

그 외는 호스트 앱이 IKekCredentialProvider를 구현한다.

Credentials (OS 자격 증명, RID별 런타임 자산)

소비자는 **Biz.Bizadm.KMS.Credentials**만 참조한다. 패키지 내부의 runtimes/{rid}/lib/net10.0 자산에서 현재 RID에 맞는 구현을 선택한다.

구성 요소 용도
Biz.Bizadm.KMS.Credentials facade와 RID별 런타임 자산을 포함한 소비자용 NuGet 패키지
runtimes/win/lib/net10.0/* Windows Credential Manager (wincredman) 구현
runtimes/osx/lib/net10.0/* macOS Keychain (keychain) 구현
runtimes/linux/lib/net10.0/* Linux Secret Service (secretservice) 구현

Biz.Bizadm.KMS.Credentials.Runtime.* 프로젝트는 facade 패키지에 포함되는 내부 구현을 빌드하기 위한 프로젝트입니다. 소비자가 런타임 패키지를 직접 참조할 필요는 없습니다.

IOsKekCredentialStore creds = OsKekCredentialProvider.CreateForCurrentOs();
creds.StorePassword("secret"u8);
using AesGcmKekCipher kek = AesGcmKekCipher.Create(creds, salt, iterations);

Linux 헤드리스에서는 프로세스 시작 전에 GCM_CREDENTIAL_STORE=gpg 등을 설정할 수 있다.

IKekCipher

구현체는 키 저장 방식만 다르고, 호출부는 동일하다. 대량 데이터 암호화가 아니라 DEK wrap 용도를 전제로 한다.

구현 알고리즘 출력
AesGcmKekCipher PBKDF2-SHA256으로 유도 AES-256-GCM (System.Security.Cryptography) nonce(12) \|\| cipher \|\| tag(16)
TpmKekCipher TPM 내부 KEK (SRK 자식) AES-256-CFB(기본) 또는 RSA-OAEP-256 AES: iv(16) \|\| cipher / RSA: keySize/8 바이트
AzureKeyVaultKekCipher Key Vault RSA-4096 (Wrap/Unwrap) RSA-OAEP-256 (CryptographyClient) wrap된 키 바이트
Pkcs11KekCipher HSM RSA-4096 (토큰 영구 키) RSA-OAEP-256 (C_WrapKey/C_UnwrapKey) wrap된 키 바이트

AesGcmKekCipher

소프트웨어 KEK. BouncyCastle을 쓰지 않으며 .NET AesGcm + Rfc2898DeriveBytes.Pbkdf2를 사용한다.

  • 생성: AesGcmKekCipher.Create(IKekCredentialProvider, salt, iterationCount) → 32바이트 키
  • 로테이션: AesGcmKekCipher.CreateRotated(IKekCredentialProvider, newSalt, iterationCount)
  • KeyId: aesgcm:{SHA256(derivedKey)} — 패스워드·salt·iteration 변경 시 함께 바뀐다
  • AesGcm 인스턴스는 ObjectPool로 재사용 (AbstractAesGcmCipher)
  • AEAD이므로 변조 시 AuthenticationTagMismatchException
  • Decrypt 실패 시 평문 버퍼를 ZeroMemory로 지운 뒤 예외를 다시 던진다

AesGcmDekCipher

DEK로 데이터를 암·복호화한다. DEK 자체는 IKekCipher로 wrap되어 envelope 파일 또는 바이트로 보관된다.

  • Create(IKekCipher, FileInfo) — 파일이 있으면 envelope 로드, 없으면 32바이트 DEK를 생성·wrap 후 원자적 저장
  • Create(IKekCipher, byte[] envelope) — envelope 바이트에서 생성 (cipher.KeyId와 envelope KeyId 일치 검증)
  • Rewrap(sourceKek, targetKek, FileInfo|byte[]) — KEK rotation 후 DEK re-wrap
  • 파일 저장은 temp 파일 기록 후 File.Move로 원자적으로 반영한다. 경쟁으로 파일이 이미 생기면 기존 파일을 다시 로드한다.
  • Dispose 시 DEK 키를 ZeroMemory한다 (AbstractAesGcmCipher)

AzureKeyVaultKekCipher

Azure Key Vault RSA 키로 DEK를 wrap/unwrap한다. Azure.Security.KeyVault.Keys + Azure.IdentityTokenCredential을 사용한다.

생성: AzureKeyVaultKekCipher.CreateAsync(Uri vaultUri, TokenCredential credential, string keyName, string? version = null, CancellationToken).

  • version을 지정하면 해당 버전 로드(unwrap용). null이면 최신 버전(wrap용).

  • RotateAsync() — Key Vault에서 동일 keyName으로 새 RSA 키 버전(CreateRsaKeyAsync) 생성 후 새 인스턴스 반환

  • KeyId: azurekv:{keyName}:{version}

  • 지정한 이름의 RSA-4096 키를 로드하고, 없으면 WrapKey/UnwrapKey 전용으로 생성한다.

  • 409(충돌)·soft-delete된 키가 있으면 재조회 또는 recover 후 사용한다.

  • Encrypt/EncryptAsyncWrapKey(RsaOaep256), Decrypt/DecryptAsyncUnwrapKey(RsaOaep256)

  • 키 물질은 Vault에만 있고 로컬에 내려오지 않는다.

using AzureKeyVaultKekCipher kek = await AzureKeyVaultKekCipher.CreateAsync(
    vaultUri, credential, "my-kek-key");
using AesGcmDekCipher dek = AesGcmDekCipher.Create(kek, new FileInfo("dek.bin"));

AzureKeyVaultKekCredentialProvider

Azure Key Vault Secret에 보관한 32바이트 패스워드를 IKekCredentialProvider로 제공한다. AesGcmKekCipher 등과 함께 쓴다.

  • 생성: new AzureKeyVaultKekCredentialProvider(Uri vaultUri, TokenCredential credential, string secretName)
  • Secret이 없으면 랜덤 32바이트를 Base64로 저장한 뒤 재조회한다. 동시 생성·soft-delete는 cipher와 같이 재조회/recover로 처리한다.
  • GetPassword / GetPasswordAsync 모두 지원한다. 반환 바이트는 호출부가 사용 후 ZeroMemory해야 한다.

PKCS#11 HSM (Biz.Bizadm.KMS.Pkcs11)

Thales Luna·nCipher·SoftHSM2 등 PKCS#11 호환 HSM으로 DEK를 wrap/unwrap한다. 메인 Biz.Bizadm.KMS와 별도 NuGet 패키지이며, Pkcs11Interop를 사용한다.

검증 범위: 현재 Pkcs11KekCipher·Pkcs11KekManagerSoftHSM 2.5.0(소프트웨어 에뮬레이터) Manual 테스트만 수행했다. Thales Luna·nCipher·AWS CloudHSM 등 실물 HSM에서는 아직 검증되지 않았다. 프로덕션 투입 전에는 대상 HSM에서 Pkcs11KekOptions(wrap 메커니즘·RSA 키 크기)를 벤더 권장값에 맞춰 별도 Manual 테스트 클래스를 추가하는 것을 권장한다.

소비자는 **Biz.Bizadm.KMS + Biz.Bizadm.KMS.Pkcs11**을 참조한다. cryptoki DLL/SO 경로는 호스트가 런타임에 지정한다.

using Biz.Bizadm.KMS.Cipher;
using Biz.Bizadm.KMS.Pkcs11.Cipher;

using Pkcs11LibraryContext context = Pkcs11LibraryContext.Create(
    @"C:\SoftHSM2\lib\softhsm2-x64.dll",
    slotId: 0,
    pinProvider);
using Pkcs11KekManager manager = Pkcs11KekManager.Create(context, "my-kek-label");
using AesGcmDekCipher dek = AesGcmDekCipher.Create(manager.Current, new FileInfo("dek.bin"));

string oldKeyId = manager.Current.KeyId;
manager.Rotate("my-kek-label-v2");
manager.RewrapDekFile(new FileInfo("dek.bin"));
manager.Release(oldKeyId);

Pkcs11LibraryContext

  • PKCS#11 라이브러리 로드, 슬롯 선택, USER PIN 로그인(IKekCredentialProvider)
  • RW 세션 1개 + SemaphoreSlim으로 HSM 호출 직렬화 (동시 호출 금지)
  • Pkcs11KekManager가 컨텍스트 수명을 관리한다. Cipher만 단독 사용 시 호출부가 Dispose한다.

Pkcs11KekCipher

  • 생성: Pkcs11KekCipher.Create(context, keyLabel, createIfMissing: true)
  • HSM에서 CKA_LABEL로 RSA 키 쌍을 찾고, 없으면 RSA-4096을 생성한다.
  • Encrypt/Decrypt — DEK(32바이트)를 일시 AES 객체로 만든 뒤 RSA-OAEP-256으로 C_WrapKey/C_UnwrapKey
  • Rotate(newKeyLabel) — HSM에 새 RSA 키 쌍 생성 후 새 인스턴스 반환
  • KeyId: pkcs11:{SHA256(modulus||exponent)}
  • Pkcs11KekOptions로 OAEP 메커니즘·RSA 키 크기를 벤더에 맞게 조정할 수 있다.
  • KEK 개인키 물질은 HSM 밖으로 나오지 않는다.

SoftHSM2 로컬 설정 예시

# Linux 예시
export SOFTHSM2_CONF=$HOME/softhsm2.conf
softhsm2-util --init-token --slot 0 --label "kms-test" --pin 1234 --so-pin 0000

# 키 확인
pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so --login --pin 1234 --list-objects

Windows portable 예시 (SOFTHSM2_CONF·PATH 설정 후):

$base = "C:\path\to\SoftHSM2"
$env:SOFTHSM2_CONF = "$base\etc\softhsm2.conf"
$env:PATH = "$base\bin;$base\lib;" + $env:PATH
& "$base\bin\softhsm2-util.exe" --init-token --slot 0 --label "kms-test" --pin 1234 --so-pin 00000000

수동 테스트 환경 변수:

변수 예시 비고
PKCS11_LIBRARY_PATH ...\lib\softhsm2-x64.dll 64비트 .NET
PKCS11_PIN 1234 --init-token 시 지정한 USER PIN
PKCS11_SLOT_ID 1590757401 생략 가능 — 초기화된 토큰 슬롯 자동 탐색

SoftHSM 2.5.0 Manual 테스트 프로파일: Pkcs11KekCipher 기본값(RSA-OAEP-256)은 SoftHSM 2.5.0에서 CKR_ARGUMENTS_BAD가 날 수 있다. 테스트는 SoftHsmPkcs11TestProfileCKM_RSA_PKCS + RSA-2048 옵션을 주입한다. 프로덕션 HSM 검증은 벤더별로 별도 프로파일·Manual 테스트 클래스를 추가한다.

$env:PKCS11_LIBRARY_PATH = "C:\path\to\SoftHSM2\lib\softhsm2-x64.dll"
$env:PKCS11_PIN = "1234"
dotnet test --filter "FullyQualifiedName~SoftHsm"

TpmKekCipher

TPM 2.0에 KEK를 두고 암·복호화한다. TSS.Net (Microsoft.TSS) 사용.

생성: TpmKekCipher.Create(Tpm2Device device, IKekCredentialProvider credentialProvider, FileInfo kekBlobFile, TpmKekOptions? options = null). 디바이스 연결은 호출부가 한다.

  • Rotate(FileInfo newKekBlobFile, TpmKekOptions? options = null) — 동일 SRK 아래 새 KEK blob 생성
  • KeyId: tpm:{SHA256(kekPublic)}
  • Dispose 시 TPM 핸들(SRK·KEK)만 flush한다. Tpm2Device는 닫지 않는다 — TSS.Net Tpm2.Dispose()가 device까지 Dispose하기 때문에, 공유 device를 쓰는 Rotate·Release 시나리오에서 연결이 끊기지 않도록 의도적으로 생략했다. device Close/Dispose는 호출부 책임이다.

Wrap 모드 (TpmKekOptions)

보안 우선(KEK wrap 무결성·변조 검출)이면 **RsaOaep256**을 권장한다. Aes256Cfb는 성능 우선 모드로 인증 태그가 없으며 wrap된 DEK 변조를 검출하지 못한다(README AES-CFB 섹션 참고).

모드 KEK 타입 wrap 알고리즘 출력
Aes256Cfb (기본) TPM AES-256 대칭키 EncryptDecrypt AES-256-CFB iv(16) \|\| cipher
RsaOaep256 (보안 권장) TPM RSA 비대칭키 (기본 2048비트) RsaEncrypt / RsaDecrypt OAEP-SHA256 keySize/8 바이트

기존 blob을 로드할 때는 TpmPublic.type으로 모드를 자동 감지한다 (Symcipher → AES, Rsa → RSA-OAEP). AES blob과 RSA blob은 호환되지 않으므로 모드를 바꾸려면 새 blob을 만들어야 한다.

var rsaOptions = new TpmKekOptions { WrapMode = TpmKekWrapMode.RsaOaep256 };
using TpmKekCipher kek = TpmKekCipher.Create(device, creds, kekBlobFile, rsaOptions);

키 계층

  1. SRK — Owner 계층 CreatePrimary. RSA-2048 storage parent (restricted decrypt). password의 SHA-256을 unique에 넣어 동일 패스워드면 같은 SRK가 나온다. SRK authValueHMAC-SHA256(password, "Biz.Bizadm.KMS.Tpm.Srk")로 유도한다.
  2. KEK — SRK 아래 대칭(AES-256-CFB) 또는 비대칭(RSA-OAEP) 키. authValueHMAC-SHA256(password, "Biz.Bizadm.KMS.Tpm.Kek"). blob이 없으면 TpmKekOptions.WrapMode에 따라 생성 후 저장, 있으면 blob에서 Load. Create·Load·Encrypt/Decrypt는 TSS.Net handle auth로 자동 인증한다.

패스워드는 Create 시 1회만 GetPassword()로 받고 authValue 유도 후 즉시 ZeroMemory한다. 인스턴스 수명 동안 TPM handle auth가 유지되므로 매 연산마다 password를 다시 넣지 않는다.

Blob (TpmKekBlob)

파일 매직 TKEK, version 1, TPM TpmPublic / TpmPrivate 직렬화. 개인키 물질은 SRK로 wrap된 채 디스크에 저장되고, 올바른 password(SRK·KEK authValue + SRK unique)로만 Load된다.

FileInfo.Exists는 캐시되므로 생성·로드 경로는 Refresh() 후 존재 여부를 본다.

AES-256-CFB (기본)

TPM 2.0은 GCM을 지원하지 않아 CFB를 쓴다.

  • Encrypt: 랜덤 IV 16바이트 + EncryptDecrypt(encrypt)
  • Decrypt: IV 분리 후 EncryptDecrypt(decrypt)
  • 인증 태그 없음. 변조되어도 TPM이 성공하고 다른 평문을 반환할 수 있다.

일부 Windows 실물 TPM은 export 규제 등으로 EncryptDecrypt를 막아 두기도 한다. 이 환경의 TBS 테스트에서는 AES-CFB 라운드트립이 통과했다.

RSA-OAEP-256

  • Encrypt: RsaEncrypt(OAEP-SHA256) — DEK(32바이트)를 직접 wrap
  • Decrypt: RsaDecrypt(OAEP-SHA256)
  • OAEP 패딩으로 변조 시 복호화가 실패한다 (TpmException)
  • RSA-2048 기준 최대 평문 ~190바이트 (DEK 32바이트에 충분)

TPM 디바이스

TpmKekCipherTpm2Device만 받는다. 연결은 호출부가 한다.

Windows에서는 TSS.Net TbsDevice + Connect()로 로컬 TPM(TBS)에 연결한다. TpmKekCipher는 기본 AES-256-CFB 또는 TpmKekOptions로 RSA-OAEP-256을 선택할 수 있다.

테스트

자동 테스트(CI / dotnet test):

dotnet test --filter "TestCategory!=Manual"
  • AesGcmKekCipherTests
  • AesGcmKekCipherPerformanceTests
  • AesGcmDekCipherTests
  • WrappedDekEnvelopeTests
  • KekCipherExtensionsTests
  • AesGcmKekManagerTests

수동 테스트: [TestCategory("Manual")]만 붙인다 ([Ignore] 없음). CI에서는 위 필터로 제외하고, Visual Studio Test Explorer에서는 해당 클래스를 선택해 바로 실행한다. Run All에서 Manual까지 빼려면 검색창에 -Trait:"Manual"을 쓴다.

TPM 기능·성능 테스트는 디바이스별 파생 클래스로 나뉜다. PKCS#11도 동일하게 추상 베이스(Pkcs11KekCipherDeviceTests)에 공통 시나리오를 두고, SoftHSM 연결만 파생 클래스에서 구현한다. 자격 증명은 테스트용 FixedPasswordCredentialProvider를 쓴다.

클래스 베이스 대상
TpmKekCipherAesTbsDeviceTests TpmKekCipherDeviceTests Windows TBS AES-256-CFB ([OSCondition(Windows)])
TpmKekCipherRsaOaepTbsDeviceTests TpmKekCipherDeviceTests Windows TBS RSA-OAEP-256 ([OSCondition(Windows)])
TpmKekCipherAesTbsDevicePerformanceTests TpmKekCipherDevicePerformanceTests TBS AES 성능 (횟수·지연 한도를 하드웨어에 맞춤)
TpmKekCipherRsaOaepTbsDevicePerformanceTests TpmKekCipherDevicePerformanceTests TBS RSA-OAEP 성능
AzureKeyVaultKekCipherTests Key Vault RSA wrap·Rotate ([OSCondition(Windows)], 환경 변수·클라이언트 인증서 필요)
AzureKeyVaultKekCipherPerformanceTests Key Vault RSA wrap 성능 ([OSCondition(Windows)])
AzureKeyVaultKekManagerTests Manager Rotate + re-wrap ([OSCondition(Windows)])
AzureKeyVaultKekCredentialProviderTests Key Vault Secret 패스워드 ([OSCondition(Windows)])
Pkcs11KekCipherSoftHsmTests Pkcs11KekCipherDeviceTests SoftHSM2 (SoftHsmPkcs11TestProfile, CKM_RSA_PKCS)
Pkcs11KekManagerSoftHsmTests Pkcs11KekManagerDeviceTests Manager Rotate + re-wrap
Pkcs11KekCipherSoftHsmPerformanceTests SoftHSM2 wrap 성능 ([TestCategory("Manual")])

Azure 수동 테스트 환경 변수: AZURE_KEY_VAULT_URL, AZURE_TENANTID, AZURE_CLIENT_ID, AZURE_KEY_VAULT_THUMBPRINT(CurrentUser\My 인증서).

PKCS#11(SoftHSM) 수동 테스트 환경 변수: PKCS11_LIBRARY_PATH, PKCS11_PIN. PKCS11_SLOT_ID는 생략 가능.

Pkcs11KekCipher 관련 Manual 테스트는 SoftHSM 2.5.0만 대상이다. 실물 HSM용 테스트 클래스는 아직 없다.

테스트 blob은 %TEMP%\kms-tpm-kek-*.blob / kms-tpm-kek-perf-*.blob에 만들었다가 종료 시 삭제한다.

TBS AES 기능 테스트는 이 환경에서 18건 통과했다.

swtpm Manual 테스트 (SwtpmTpmDevice)

Rocky Linux swtpm TCP 검증용 어댑터는 Biz.Bizadm.KMSTest/Cipher/Tpm/Device/SwtpmTpmDevice.cs에 있다. Microsoft TcpTpmDevice(mssim handshake)와는 프로토콜이 다르다. swtpm 검증이 필요하면 TpmKekCipherDeviceTests를 파생하고 이 디바이스로 Tpm2Device를 연결하면 된다.

  • 명령 포트: raw TPM 프레임 (기본 2321)
  • not-need-init이면 control CMD_INIT 불필요
  • startup-clear가 없으면 Connect()에서 TPM2_Startup(SU_CLEAR) (이미 started면 TPM_RC_INITIALIZE 무시)
swtpm socket \
    --tpm2 \
    --tpmstate dir=/swtpm \
    --server type=tcp,port=2321,bindaddr=0.0.0.0 \
    --ctrl type=tcp,port=2322,bindaddr=0.0.0.0 \
    --flags not-need-init \
    --daemon

disconnect가 없으면 연결을 유지한다. 한 연결에서 명령을 직렬 처리하므로 TpmKekCipher 동시 호출은 하지 않는다.

성능 참고 (실측)

측정 대상은 32바이트 DEK wrap/unwrap 라운드트립(Encrypt + Decrypt)이다. 성능 테스트는 Biz.Bizadm.KMSTest에 있으며, 아래 수치는 이 저장소를 개발한 Windows 환경(net10.0)에서 dotnet test --logger "console;verbosity=detailed"로 재현할 수 있다.

AesGcmKekCipher (소프트웨어 AES-256-GCM)

테스트: AesGcmKekCipherPerformanceTests (CI 포함)

항목
동시성 degree = ProcessorCount × 2 (본 환경 32)
처리량 40,000 ops, p50 ≈ 0.001ms, p95 ≈ 0.002ms, ≈ 2.2×10⁷ ops/s
할당 144 bytes/roundtrip (관리 힙)

DEK wrap 용도로는 사실상 CPU-bound이며 서브밀리초다.

TpmKekCipher — AES-256-CFB (Windows 실물 TPM)

테스트: TpmKekCipherAesTbsDevicePerformanceTests ([TestCategory("Manual")], [OSCondition(Windows)])

항목
디바이스 TbsDevice + Connect() (로컬 TPM)
처리량 20 ops 순차, p50 ≈ 31ms, p95 ≈ 47ms, ≈ 33 ops/s
할당 340KiB/roundtrip (TSS.Net 마샬링; secondGrowth=0으로 누수는 관측되지 않음)

TpmKekCipher — RSA-OAEP-256 (Windows 실물 TPM)

테스트: TpmKekCipherRsaOaepTbsDevicePerformanceTests ([TestCategory("Manual")], [OSCondition(Windows)])

항목
디바이스 TbsDevice + Connect() (로컬 TPM), RSA-2048 KEK
처리량 20 ops 순차, p50 ≈ 47ms, p95 ≈ 61ms, ≈ 21 ops/s
할당 387KiB/roundtrip (TSS.Net 마샬링; secondGrowth=0으로 누수는 관측되지 않음)

동일 TPM·동일 32바이트 DEK 기준으로 RSA-OAEP는 AES-CFB 대비 p50 약 1.5배 느리다. TPM은 소프트웨어 KEK 대비 약 3만 배 느리지만, DEK wrap처럼 저빈도 호출에는 실사용 가능한 수준이다. 동일 연결에서 동시 호출은 하지 않는다.

AzureKeyVaultKekCipher (Key Vault RSA-OAEP-256)

테스트: AzureKeyVaultKekCipherPerformanceTests ([TestCategory("Manual")], [OSCondition(Windows)])

항목
인증 클라이언트 인증서 (AZURE_KEY_VAULT_* 환경 변수)
처리량 30 ops 순차, p50 ≈ 46ms, p95 ≈ 274ms, p99 ≈ 414ms, ≈ 12 ops/s
할당 28KiB/roundtrip (HTTPS·SDK 버퍼)

Key Vault HTTPS 왕복이 지배적이며, 리전·네트워크에 따라 변동한다. 키 물질은 Vault에만 존재한다.

비교 요약

구현 p50 (wrap+unwrap) 상대 처리량 비고
AesGcmKekCipher ≈ 0.001ms 기준 (최고) 로컬 CPU, 동시 처리 가능
TpmKekCipher AES-256-CFB ≈ 31ms ≈ 1/30,000 TBS 직렬, 할당 큼
TpmKekCipher RSA-OAEP-256 ≈ 47ms ≈ 1/47,000 동일 TPM에서 AES 대비 ≈1.5× 느림, 변조 시 실패
AzureKeyVaultKekCipher ≈ 46ms ≈ 1/46,000 HTTPS + RSA, 네트워크 bound
Pkcs11KekCipher (SoftHSM 2.5.0) ≈ 2.4ms (CKM_RSA_PKCS) Pkcs11KekCipherSoftHsmPerformanceTests (Manual). 에뮬레이터 수치이며 실물 HSM과 무관
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.2 40 9/15/2026