비밀번호 저장과 데이터 암호화 - bcrypt부터 AES-GCM까지
보안을 위해 비밀번호를 저장하거나 데이터를 암호화할 때, 자칫하면 큰 보안 홀을 만들 수 있다. 나중에 개발할 때 실수하지 않도록, bcrypt와 AES-GCM을 활용한 실무 지침들을 정리해본다.
이론적인 깊이보다는, 구현 과정에서 놓치기 쉬운 실무적인 포인트들을 기록해두는 데 목적을 둔다.
1. 비밀번호 저장
자주 헷갈리는 부분
// [위험] SHA-256으로 비밀번호 저장
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(password));
// [위험] MD5로 비밀번호 저장
var hash = MD5.HashData(Encoding.UTF8.GetBytes(password));
// [위험] AES로 비밀번호 암호화
var encrypted = Aes.Encrypt(password, key);왜 안 되냐면:
| 방식 | 문제 |
|---|---|
| SHA-256 | 너무 빨라서 무차별 대입에 취약 |
| MD5 | 빠름 + 충돌 발견됨. 그냥 쓰면 안 됨 |
| AES 암호화 | 복호화 가능 → 키 털리면 끝 |
느린 해시를 써야 하는 이유
비밀번호 해시는 빠르면 안 된다. 공격자가 초당 수십억 개씩 돌릴 수 있으면 어떤 비밀번호든 결국 뚫린다.
그래서 일부러 느리게 만든 해시 함수를 쓴다.
| 알고리즘 | 속도 | Salt | 권장 여부 |
|---|---|---|---|
| MD5 | 초당 수십억 | 지원 안 함 | 지양 |
| SHA-256 | 초당 수십억 | 직접 관리 필요 | 지양 |
| bcrypt | 초당 수천 | 내장됨 | 권장 |
| Argon2 | 초당 수백 | 내장됨 | 권장 |
bcrypt 사용법
// 해시 생성 (workFactor가 높을수록 느림)
var hash = BCrypt.Net.BCrypt.HashPassword(password, workFactor: 12);
// 검증
var isValid = BCrypt.Net.BCrypt.Verify(inputPassword, storedHash);workFactor는 보통 12 정도면 된다. 13-14는 보안 요구사항 높을 때, 10 이하는 테스트용.
bcrypt 결과물:
$2a$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/X4.iANz2vJkW6LmGi
│ │ │
│ │ └── Salt + Hash
│ └── Work Factor
└── 알고리즘 버전
Salt가 해시 안에 들어있어서 따로 관리 안 해도 된다.
Argon2
bcrypt 다음 세대 알고리즘이다. 메모리 사용량도 조절할 수 있어서 GPU 공격에 더 강하다.
using Konscious.Security.Cryptography;
var argon2 = new Argon2id(Encoding.UTF8.GetBytes(password))
{
Salt = salt,
DegreeOfParallelism = 4,
MemorySize = 65536, // 64MB
Iterations = 3
};
var hash = argon2.GetBytes(32);새 프로젝트면 Argon2 쓰는 게 좋고, 이미 bcrypt 쓰고 있으면 굳이 옮길 필요는 없다. 둘 다 충분히 안전하다.
2. 데이터 암호화 - AES
비밀번호가 아닌 일반 데이터를 암호화할 때는 대칭키 암호화를 쓴다. 대표적인 게 AES.
AES 모드
AES 자체는 블록 암호화고, 실제 사용할 때는 모드를 선택해야 한다.
| 모드 | 무결성 검증 | 권장 여부 |
|---|---|---|
| ECB | 지원 안 함 | 지양 |
| CBC | 지원 안 함 (별도 필요) | 레거시 호환용 |
| GCM | 지원함 | 권장 |
ECB가 안 되는 이유
같은 평문 블록이 같은 암호문을 만든다. 패턴이 그대로 드러남.
평문: [AAA][AAA][BBB][AAA]
ECB: [XXX][XXX][YYY][XXX] ← 패턴 노출
CBC/GCM: [XXX][ZZZ][YYY][QQQ] ← 패턴 숨김
"ECB 펭귄"으로 검색하면 이미지 나온다. 펭귄 사진을 ECB로 암호화하면 윤곽이 그대로 보인다.
CBC 한계
패턴은 숨기는데 무결성 검증이 없다.
암호문 일부 변경 → 복호화 성공 → 변조된 평문이 그대로 사용됨
CBC 쓰려면 별도로 HMAC을 붙여야 하는데 번거롭다.
AES-GCM
GCM은 AEAD(Authenticated Encryption with Associated Data) 모드다. 암호화랑 무결성 검증이 한 번에 된다.
using System.Security.Cryptography;
public static (byte[] ciphertext, byte[] tag, byte[] nonce) Encrypt(
byte[] plaintext, byte[] key)
{
var nonce = new byte[12];
RandomNumberGenerator.Fill(nonce);
var ciphertext = new byte[plaintext.Length];
var tag = new byte[16];
using var aes = new AesGcm(key, 16);
aes.Encrypt(nonce, plaintext, ciphertext, tag);
return (ciphertext, tag, nonce);
}
public static byte[] Decrypt(
byte[] ciphertext, byte[] key, byte[] nonce, byte[] tag)
{
var plaintext = new byte[ciphertext.Length];
using var aes = new AesGcm(key, 16);
aes.Decrypt(nonce, ciphertext, tag, plaintext);
return plaintext;
}GCM 쓰면:
- 변조된 암호문은 복호화할 때 예외 발생
- 별도 HMAC 안 붙여도 됨
Nonce 주의
GCM에서 같은 키로 같은 Nonce를 쓰면 보안이 깨진다.
[위험] 고정 Nonce 사용
[위험] 카운터 기반 Nonce (충돌 위험)
[권장] 매번 랜덤 12바이트 생성
Nonce는 암호문이랑 같이 저장/전송하면 된다. 비밀 아니다.
3. 키 관리
암호화 알고리즘보다 키 관리가 더 어렵다.
하면 안 되는 것
// [지양] 코드 하드코딩
private const string Key = "my-super-secret-key-123";
// [지양] 설정 파일에 평문 저장
{
"EncryptionKey": "my-super-secret-key-123"
}
// [지양] 환경 변수에 평문 노출 (로그에 찍힐 우려)이렇게 하자
| 환경 | 방법 |
|---|---|
| 로컬 | .NET User Secrets, dotenv |
| 클라우드 | AWS KMS, Azure Key Vault |
| 온프레미스 | HashiCorp Vault |
AWS KMS 예시:
var kmsClient = new AmazonKeyManagementServiceClient();
var response = await kmsClient.GenerateDataKeyAsync(new GenerateDataKeyRequest
{
KeyId = "alias/my-master-key",
KeySpec = DataKeySpec.AES_256
});
var plaintextKey = response.Plaintext; // 암호화에 사용
var encryptedKey = response.CiphertextBlob; // 저장용Envelope Encryption 패턴:
- 마스터 키는 KMS가 관리
- 데이터 키는 마스터 키로 암호화해서 저장
- 실제 데이터는 데이터 키로 암호화
정리
비밀번호
| 상황 | 선택 |
|---|---|
| 새 프로젝트 | Argon2id |
| 기존 프로젝트 | bcrypt |
| 지양 | SHA-256, MD5, 비밀번호 암호화(AES 등) |
데이터 암호화
| 상황 | 선택 |
|---|---|
| 일반 | AES-256-GCM |
| 레거시 호환 | AES-CBC + HMAC |
| 지양 | AES-ECB |
키 관리
| 환경 | 선택 |
|---|---|
| 클라우드 | KMS / Key Vault |
| 온프레미스 | Vault |
| 로컬 | User Secrets |
단순한 지식 전달보다는, 나중에 직접 구현할 때 헤매지 않도록 핵심 위주로 정리해둔다. 보안은 한 번의 실수로 공든 탑이 무너질 수 있는 만큼, 기록해둔 내용들을 꼼꼼히 복기하며 설계해야겠다.
Loading comments...