using System.Security.Cryptography; using System.Text; namespace Infrastructure.Kyc.Nice; /// /// 의 invariants self-check. /// /// dev 환경 부팅 시 1회 실행해서 PBKDF2 출력 길이, AES-GCM 라운드트립, HMAC 라운드트립이 /// 깨지지 않았음을 확인. 라이브러리/런타임 업데이트로 silently 동작이 바뀌는 회귀를 방지. /// /// 진짜 NICE 호환성 검증은 별도 phase (xUnit + NICE 공식 PHP/Node.js sample 의 골든벡터) 에서. /// public static class NiceCryptoInvariants { /// /// 실패 시 throw. /// public static void EnsureOrThrow() { EnsureBase64UrlRoundTrip(); EnsureDeriveKeysShape(); EnsureHmacRoundTrip(); EnsureAesGcmRoundTrip(); } private static void EnsureBase64UrlRoundTrip() { // 패딩 길이 0/1/2 byte 모두 라운드트립 var samples = new[] { new byte[] { 0x00 }, new byte[] { 0x01, 0x02 }, new byte[] { 0xFF, 0xFE, 0xFD }, Encoding.UTF8.GetBytes("hello world") }; foreach (var s in samples) { var encoded = NiceCrypto.Base64UrlEncode(s); if (encoded.Contains('+') || encoded.Contains('/') || encoded.EndsWith('=')) { throw new CryptographicException($"Base64UrlEncode 가 URL-safe + no-padding 규칙을 위반: '{encoded}'"); } var decoded = NiceCrypto.Base64UrlDecode(encoded); if (!decoded.AsSpan().SequenceEqual(s)) { throw new CryptographicException("Base64Url 라운드트립 실패."); } } } private static void EnsureDeriveKeysShape() { // 임의 vector — NICE 호환 vector 아님. 길이/결정성만 검증. var (aesKey, hmacKey) = NiceCrypto.DeriveKeys("ticket-self-check", "transaction-id-self-check", 1000); if (aesKey.Length != 32) { throw new CryptographicException($"DeriveKeys: AES key length = {aesKey.Length} (expected 32)."); } if (hmacKey.Length != 32) { throw new CryptographicException($"DeriveKeys: HMAC key length = {hmacKey.Length} (expected 32)."); } // 같은 입력 → 같은 출력 (deterministic) var (aesKey2, hmacKey2) = NiceCrypto.DeriveKeys("ticket-self-check", "transaction-id-self-check", 1000); if (!aesKey.AsSpan().SequenceEqual(aesKey2) || !hmacKey.AsSpan().SequenceEqual(hmacKey2)) { throw new CryptographicException("DeriveKeys 가 결정론적이지 않습니다."); } } private static void EnsureHmacRoundTrip() { var hmacKey = new byte[32]; RandomNumberGenerator.Fill(hmacKey); const string encData = "AbCdEfGhIjKlMnOpQrStUvWxYz0123456789"; var integrity = NiceCrypto.ComputeIntegrity(hmacKey, encData); if (!NiceCrypto.VerifyIntegrity(hmacKey, encData, integrity)) { throw new CryptographicException("HMAC ComputeIntegrity ↔ VerifyIntegrity 라운드트립 실패."); } // 다른 키로는 fail var otherKey = new byte[32]; RandomNumberGenerator.Fill(otherKey); if (NiceCrypto.VerifyIntegrity(otherKey, encData, integrity)) { throw new CryptographicException("HMAC VerifyIntegrity 가 잘못된 키를 통과시켰습니다."); } } private static void EnsureAesGcmRoundTrip() { var aesKey = new byte[32]; RandomNumberGenerator.Fill(aesKey); const string plaintext = "{\"name\":\"홍길동\",\"birthdate\":\"19800101\",\"gender\":\"1\"}"; // NICE 와 동일한 16 byte IV + ciphertext+tag 레이아웃 (BouncyCastle 기반). var encData = NiceCrypto.EncryptAesGcm(aesKey, plaintext); var decrypted = NiceCrypto.DecryptAesGcm(aesKey, encData); if (decrypted != plaintext) { throw new CryptographicException($"AES-GCM 라운드트립 실패. expected='{plaintext}', got='{decrypted}'"); } // 위변조 검출: 임의 1 byte 변경 시 throw var tamperedBytes = NiceCrypto.Base64UrlDecode(encData); tamperedBytes[tamperedBytes.Length - 1] ^= 0x01; var tampered = NiceCrypto.Base64UrlEncode(tamperedBytes); try { NiceCrypto.DecryptAesGcm(aesKey, tampered); throw new CryptographicException("AES-GCM 위변조된 데이터를 무사 통과시켰습니다."); } catch (Org.BouncyCastle.Crypto.InvalidCipherTextException) { // 정상 — GCM tag 가 위변조 감지 } } }