API 키, 연결 문자열 같은 민감한 설정값은 평문 저장을 피하고 암호화하거나 시크릿 매니저를 사용해야 합니다. 이 글은 C#에서 암호화된 설정값을 안전하게 로드하는 실용적인 패턴을 정리합니다.
1. 기본 원칙
- 키와 사이퍼텍스트를 분리합니다(예: 키는 환경 변수/키 금고, 값은 파일).
- 저장소(Git)에는 평문 시크릿을 올리지 않습니다.
- 복호화는 필요한 시점에만 수행하고, 메모리에 오래 보관하지 않습니다.
- 키는 회전 가능하게 설계하고, 실패 시 안전하게 종료합니다.
2. Windows라면 DPAPI로 간단히
Windows 전용 시나리오에서는 DPAPI(ProtectedData)로 현재 사용자/머신 범위로 값 보호가 쉽습니다. 초기 1회 암호화 값(Base64)을 만들어 설정 파일에 저장하고, 런타임에 복호화합니다.
// 1) 오프라인/초기화 때 암호화(Windows 전용)
using System;
using System.Security.Cryptography;
using System.Text;
string plain = "P@ssw0rd!";
byte[] protectedBytes = ProtectedData.Protect(
Encoding.UTF8.GetBytes(plain),
optionalEntropy: null,
scope: DataProtectionScope.CurrentUser);
string base64 = Convert.ToBase64String(protectedBytes);
Console.WriteLine($"DPAPI 암호문(Base64): {base64}");
// 2) 앱에서 복호화
string encBase64 = Environment.GetEnvironmentVariable("DB_PW_DPAPI_B64")!; // 또는 설정 파일에 저장된 Base64
byte[] cipher = Convert.FromBase64String(encBase64);
byte[] plainBytes = ProtectedData.Unprotect(cipher, null, DataProtectionScope.CurrentUser);
string password = Encoding.UTF8.GetString(plainBytes);
장점: 키 관리가 불필요합니다. 단점: Windows에 종속됩니다.
3. 크로스플랫폼: AES-GCM + 환경 변수 키
.NET(코어 3.0+)에서 AesGcm을 사용하면 검증 가능한 최신 대칭키 암호화를 사용할 수 있습니다. 키는 환경 변수나 키 금고(Azure Key Vault 등)에서 가져옵니다.
// 1) 키 생성(1회). Base64로 저장해 환경 변수(APP_AES_KEY)에 주입하세요.
using System;
using System.Security.Cryptography;
byte[] key = RandomNumberGenerator.GetBytes(32); // 256-bit
Console.WriteLine(Convert.ToBase64String(key));// 2) AES-GCM 헬퍼
using System;
using System.Security.Cryptography;
using System.Text;
public static class CryptoGcm
{
// 토큰 형식: "enc:gcm:" + Base64(nonce | cipher | tag)
public static string Encrypt(string plaintext, byte[] key)
{
byte[] nonce = RandomNumberGenerator.GetBytes(12);
byte[] plain = Encoding.UTF8.GetBytes(plaintext);
byte[] cipher = new byte[plain.Length];
byte[] tag = new byte[16];
using var aes = new AesGcm(key);
aes.Encrypt(nonce, plain, cipher, tag);
byte[] payload = new byte[nonce.Length + cipher.Length + tag.Length];
Buffer.BlockCopy(nonce, 0, payload, 0, nonce.Length);
Buffer.BlockCopy(cipher, 0, payload, nonce.Length, cipher.Length);
Buffer.BlockCopy(tag, 0, payload, nonce.Length + cipher.Length, tag.Length);
return "enc:gcm:" + Convert.ToBase64String(payload);
}
public static string Decrypt(string token, byte[] key)
{
if (string.IsNullOrWhiteSpace(token) || !token.StartsWith("enc:gcm:"))
return token;
byte[] payload = Convert.FromBase64String(token.Substring(8));
if (payload.Length < 12 + 16) throw new FormatException("잘못된 토큰 형식입니다.");
int nonceLen = 12;
int tagLen = 16;
int cipherLen = payload.Length - nonceLen - tagLen;
byte[] nonce = new byte[nonceLen];
byte[] cipher = new byte[cipherLen];
byte[] tag = new byte[tagLen];
Buffer.BlockCopy(payload, 0, nonce, 0, nonceLen);
Buffer.BlockCopy(payload, nonceLen, cipher, 0, cipherLen);
Buffer.BlockCopy(payload, nonceLen + cipherLen, tag, 0, tagLen);
byte[] plain = new byte[cipherLen];
using var aes = new AesGcm(key);
try
{
aes.Decrypt(nonce, cipher, tag, plain);
}
catch (CryptographicException)
{
throw new CryptographicException("복호화 실패(키 불일치 또는 데이터 손상)입니다.");
}
return Encoding.UTF8.GetString(plain);
}
}// 3) 사용 예: 환경 변수에서 키 로드 후 설정값 복호화
using Microsoft.Extensions.Configuration;
using System;
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: false)
.Build();
string keyB64 = Environment.GetEnvironmentVariable("APP_AES_KEY")
?? throw new InvalidOperationException("APP_AES_KEY 환경 변수를 설정하세요.");
byte[] key = Convert.FromBase64String(keyB64);
// appsettings.json: "Db:Password": "enc:gcm:..." 로 저장했다고 가정
string dbPassword = CryptoGcm.Decrypt(config["Db:Password"], key);
4. IConfiguration에 자연스럽게 통합
접근 시 자동 복호화하는 얇은 확장 메서드를 두면 호출부가 깔끔합니다.
using Microsoft.Extensions.Configuration;
using System;
public static class ConfigurationDecryptionExtensions
{
public static string GetSecret(this IConfiguration config, string key, Func<string, string> decrypt)
{
var value = config[key];
if (string.IsNullOrEmpty(value)) return value ?? string.Empty;
return value.StartsWith("enc:gcm:") ? decrypt(value) : value;
}
}
// Program.cs or composition root
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables()
.Build();
byte[] key = Convert.FromBase64String(Environment.GetEnvironmentVariable("APP_AES_KEY")!);
string conn = configuration.GetSecret("ConnectionStrings:Default", v => CryptoGcm.Decrypt(v, key));
5. 프로덕션: 클라우드 시크릿 매니저 사용
가능하면 클라우드 시크릿 매니저를 사용해 키와 값 모두를 안전하게 보관합니다. 예: Azure Key Vault + 관리형 ID.
// Azure Key Vault 연동(.NET 6+)
using Azure.Identity;
using Azure.Extensions.AspNetCore.Configuration.Secrets;
using Microsoft.Extensions.Configuration;
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: true)
.AddAzureKeyVault(new Uri("https://<your-vault-name>.vault.azure.net/"), new DefaultAzureCredential())
.AddEnvironmentVariables();
var config = builder.Build();
// Key Vault에서 'Db--Password' 시크릿을 만들면 config["Db:Password"]로 접근 가능
string dbPassword = config["Db:Password"]; // 복호화 불필요(서버 측 저장 보호)장점: 키 관리, 감사, 회전을 서비스가 지원합니다. 최소 권한의 관리형 ID로 접근을 제한하세요.
6. 체크리스트
- 저장소에는 절대 평문 시크릿을 커밋하지 않습니다.
- 키와 사이퍼텍스트를 분리하고, 환경 변수/키 금고를 사용합니다.
- AES-GCM 같은 인증된 암호를 사용하고 예외 시 안전하게 실패합니다.
- 로깅/덤프에 시크릿이 노출되지 않도록 필터링합니다.
- 정기적으로 키를 회전하고 접근 제어를 점검합니다.
상황이 단순하면 DPAPI(Windows), 범용이면 AES-GCM+환경 변수, 규모가 커지면 클라우드 시크릿 매니저를 선택하는 것이 실용적인 로드맵입니다.
'C#' 카테고리의 다른 글
| C# 메서드 오버로딩과 오버라이딩 차이 및 모범 사례 (0) | 2026.06.10 |
|---|---|
| C# 인터페이스의 명시적 구현 활용과 주의사항 (0) | 2026.06.05 |
| C# 메서드 체이닝(Method Chaining) 패턴 구현 (0) | 2026.06.04 |
| C# PriorityQueue<T>를 이용한 우선순위 작업 처리 (0) | 2026.06.04 |
| C# Reflection.Emit으로 런타임 코드 생성하기 (0) | 2026.06.04 |