저엔트로피 데이터·카드번호 해싱의 안전 경계 (APAC)
목차
불편한 사실 하나: 해싱은 보호가 아닙니다. 신용카드 번호의 SHA-256 해시를 저장하면서 완전히 PCI DSS를 준수하고도, 실질적으로는 아무런 보호도 갖지 못할 수 있습니다. 해싱된 값이 무차별 대입을 견딜 만큼 충분한 엔트로피를 담고 있지 않기 때문입니다.
평소 성실한 엔지니어링 팀들도 여기서 넘어집니다. 해싱은 안전해 보이기 때문입니다. 해시는 단방향이고 함수를 뒤집어 원본을 복구할 수 없으니 당연히 데이터가 보호되는 것 아닌가. 결함은 해시에 없습니다. 결함은 그것에 먹인 입력에 있습니다.
실제 숫자로 보는 엔트로피 문제 #
16자리 카드번호는 난수가 아닙니다. 구조가 공개되어 고정되어 있습니다:
- 처음 4~6자리는 발급사 식별번호(IIN)로, 은행 접두사이며 완전히 공개적입니다.
- 마지막 자리는 Luhn 알고리즘으로 계산되는 체크섬입니다. 1954년에 발표된 공식으로, 비밀이 아니라 오류 검출입니다.
이제 PCI DSS가 흔히 허용하는 방식으로 PAN을 마스킹합니다. 처음 46자리와 마지막 4자리만 보이고 가운데 68자리를 숨깁니다:
4532 AAXX XXXX 1234
IIN 4자리만 알려진 상태에서 남는 미지의 부분은 8자리, 최대 1억 개 값입니다. Luhn 체크섬을 적용하면 10분의 1만 살아남습니다. 실제 탐색 공간은 1천만 개입니다. 비밀번호가 아니라 매우 짧은 목록입니다.
1천만 개의 해시 테스트에 걸리는 시간? #
여기서 상황이 더 나빠집니다. SHA-256은 설계상 빠릅니다. 기가비트 속도의 무결성 검사를 위해 만들어졌지, 비밀 저장용이 아닙니다. 현대 GPU 크래킹 벤치마크는 공개적이고 재현 가능합니다:
| 하드웨어 | 대략적 SHA-256 처리량 |
|---|---|
| RTX 4090 GPU 1장 | 초당 약 85억 회 |
| RTX 4090 클러스터 4장 | 초당 약 340억 회 |
| RTX 4090 클러스터 8장 | 초당 약 680억 회 |
천만 건 시도 ÷ 초당 85억 = 대략 천분의 일초. 소비자용 GPU 한 장에서요. GPU 한 장짜리 컴퓨터도 레인보우 테이블만 있으면 눈 깜빡할 사이에 카드번호를 ‘역해시’할 수 있습니다.
결론은 냉정합니다. 준수가 곧 안전은 아닙니다. 저엔트로피 필드에서는 SHA-2(또는 SHA-3)조차, 준수하더라도, 안전하지 않습니다. 함수는 단방향이 맞지만 입력 공간이 작으면 지루할 정도로 쉽게 소진됩니다. SHA-256을 SHA-512나 SHA-3로 바꿔도 해결되지 않습니다. 똑같이 빠르니까요.
“준수"가 실제로 허용하는 것 #
PCI DSS는 실제로 PAN을 SHA-256으로 해시하라고 말하지 않습니다. 요구사항 3.5는 강력한 암호학으로 PAN을 읽을 수 없게 하라고 하며, keyed hash와 암호화를 명시적으로 언급하고, salt가 비밀로 유지되고 해시가 실질적으로 역산 불가능하다면 솔트된 해시 인덱스도 수용 가능하다고 적습니다. 문제는 1천만 값 공간 위의 소금 없는 SHA-256은 실무적으로 소진을 통한 역산과 다름없다는 점입니다. 체크리스트에는 찍혀도 요건의 취지에는 실패합니다.
마스킹(앞 4-6자리 및/또는 뒷 4자리 표시)은 별개의 통제입니다. 운영자가 보는 것을 보호하지 저장하는 것을 보호하지 않습니다. 둘은 쉽게 혼동되며, 바로 이 혼동 덕에 마스킹됐지만 솔트 없는 해시의 PAN이 프로덕션까지 흘러갑니다.
이런 데이터를 제대로 보호하는 방법 #
해법은 저엔트로피 필드를 비밀번호만큼 진지하게 다루는 것입니다. 수학적으로 똑같이 약하니까요. 선호 순서대로:
- 아예 저장하지 않습니다. PAN을 토큰화하고 실제 번호는 별도 볼트나 HSM에 둡니다. 값을 저장하지 않으면 무차별 대입할 대상 자체가 없습니다.
- 비밀 pepper를 곁들인 keyed hashing(HMAC). PAN으로 색인해야 한다면 데이터베이스 밖에 둔 고엔트로피 키로 HMAC을 사용하세요. 키가 없으면 입력 엔트로피와 무관하게 무차별 대입은 연산적으로 비현실적입니다.
- 메모리 하드 패스워드 해싱. 값으로만 값을 보호해야 하는 경우 Argon2id(RFC 9106)나 scrypt를 값마다 무작위 salt와 조율된 파라미터로 사용해 모든 추측이 실제 시간과 메모리를 소모하게 합니다. 예컨대 64MB 메모리 비용의 Argon2id는 그 0.001초짜리 전수탐색을 수개월치 GPU 시간으로 바꿉니다.
- 모든 곳에 salt와 pepper. 값마다 무작위 salt는 미리 계산된 레인보우 테이블을 꺾고, 비밀 pepper는 비밀만 유지되면 오프라인 공격을 완전히 꺾습니다.
OWASP Password Storage Cheat Sheet과 NIST SP 800-63B 모두 정확히 이 이유로 저엔트로피 비밀에 메모리 하드 함수를 권합니다.
카드 너머의 교훈 #
이는 엔트로피가 제한된 모든 고정 형식 식별자에 적용됩니다. 주민등록번호, 전화번호, 생년월일, 생성이 허술한 API 키까지. 입력 공간이 작으면 해시 함수의 속도는 적이고, “준수"는 “안전"의 동의어가 아닙니다.
저희 API & 애플리케이션 보안 점검은 코드가 민감한 값을 실제로 어떻게 저장·전송하는지 검토하며, 체크리스트는 통과했는데 실제 데이터가 노출되는 지점을 분명하게 말씀드립니다.