본문으로 건너뛰기
  1. 보안 인사이트 & 어드바이저리/

저엔트로피 데이터·카드번호 해싱의 안전 경계 (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이 프로덕션까지 흘러갑니다.

이런 데이터를 제대로 보호하는 방법 #

해법은 저엔트로피 필드를 비밀번호만큼 진지하게 다루는 것입니다. 수학적으로 똑같이 약하니까요. 선호 순서대로:

  1. 아예 저장하지 않습니다. PAN을 토큰화하고 실제 번호는 별도 볼트나 HSM에 둡니다. 값을 저장하지 않으면 무차별 대입할 대상 자체가 없습니다.
  2. 비밀 pepper를 곁들인 keyed hashing(HMAC). PAN으로 색인해야 한다면 데이터베이스 밖에 둔 고엔트로피 키로 HMAC을 사용하세요. 키가 없으면 입력 엔트로피와 무관하게 무차별 대입은 연산적으로 비현실적입니다.
  3. 메모리 하드 패스워드 해싱. 값으로만 값을 보호해야 하는 경우 Argon2id(RFC 9106)나 scrypt를 값마다 무작위 salt와 조율된 파라미터로 사용해 모든 추측이 실제 시간과 메모리를 소모하게 합니다. 예컨대 64MB 메모리 비용의 Argon2id는 그 0.001초짜리 전수탐색을 수개월치 GPU 시간으로 바꿉니다.
  4. 모든 곳에 salt와 pepper. 값마다 무작위 salt는 미리 계산된 레인보우 테이블을 꺾고, 비밀 pepper는 비밀만 유지되면 오프라인 공격을 완전히 꺾습니다.

OWASP Password Storage Cheat SheetNIST SP 800-63B 모두 정확히 이 이유로 저엔트로피 비밀에 메모리 하드 함수를 권합니다.

flowchart TD A[PAN 저장] --> B{색인에 필요?} B -- 아니오 --> C[토큰화 / 볼트 / HSM] B -- 예 --> D{비밀 키 사용 가능?} D -- 예 --> E[HMAC + pepper] D -- 아니오 --> F[Argon2id / scrypt + salt] style C stroke:#10B981,stroke-width:2px style E stroke:#10B981,stroke-width:2px style F stroke:#10B981,stroke-width:2px

카드 너머의 교훈 #

이는 엔트로피가 제한된 모든 고정 형식 식별자에 적용됩니다. 주민등록번호, 전화번호, 생년월일, 생성이 허술한 API 키까지. 입력 공간이 작으면 해시 함수의 속도는 적이고, “준수"는 “안전"의 동의어가 아닙니다.

현재 PAN이나 다른 식별자를 보호하는 방식이 걱정되신가요? 부담 없이 현황 점검을 요청하세요. LINE(@PureSecurity) 또는 이메일(hello@puresecurity.com)로 연락 주시면 됩니다.

저희 API & 애플리케이션 보안 점검은 코드가 민감한 값을 실제로 어떻게 저장·전송하는지 검토하며, 체크리스트는 통과했는데 실제 데이터가 노출되는 지점을 분명하게 말씀드립니다.