108. 솔트 (Salt)

⚠️ 이 문서는 해커가 미리 계산해 둔 거대한 해시 커닝 페이퍼(레인보우 테이블)를 무력화시키기 위해, 사용자의 비밀번호를 해시 함수에 굽기 직전에 임의의 무작위 난수(소금)를 뿌려 평문의 구조를 완전히 뭉개버리는 암호학적 핵심 방어 기법인 'Salt'를 다룹니다.

핵심 인사이트 (3줄 요약)

  1. 본질: 솔트(Salt)는 비밀번호를 데이터베이스에 안전하게 저장할 때, **사용자마다 고유하게 발급된 무작위 문자열(예: x9!@kQ)을 원래의 평문 비밀번호 앞이나 뒤에 이어 붙인 다음 해싱(Hashing)**을 수행하는 기법이다.
  2. 가치: A와 B라는 두 사용자가 똑같이 123456 이라는 바보 같은 비밀번호를 써도, 각각 뿌려진 솔트 값이 다르기 때문에 DB에는 완전히 다른 두 개의 쓰레기 암호문(해시값)이 저장되어 해커의 패턴 분석(동일 비번 찾기)을 불가능하게 만든다.
  3. 융합: 솔트 하나만으로는 GPU를 동원한 무차별 대입 공격의 엄청난 연산 속도를 막아낼 수 없다. 따라서 현대의 완벽한 패스워드 방어 아키텍처는 반드시 **솔트(Salt)**와 해시를 수만 번 반복하는 **키 스트레칭(Key Stretching)**을 융합하여 Bcrypt나 PBKDF2 형태로 굳혀져 있다.

Ⅰ. 개요 및 필요성 (Context & Necessity)

단순 해시 함수(SHA-256)는 결정론적이다. 평문이 똑같으면 무조건 해시 결과도 똑같다. 만약 회사 DB가 해킹당해서 유저 1만 명의 해시 테이블이 통째로 털렸다고 치자. 해커가 쭉 훑어보니 8d969... 로 시작하는 해시값이 무려 500명에게서 똑같이 발견되었다. 해커는 속으로 쾌재를 부른다. "아싸, 이 500명은 100% 123456 같은 가장 뻔한 비밀번호를 돌려쓰는 멍청이들이구나!"

게다가 해커의 하드디스크에는 전 세계의 뻔한 비밀번호를 미리 다 해시로 구워놓은 1TB짜리 레인보우 테이블이 있다. 단순 해시로 저장된 패스워드는 이 테이블에 검색(Ctrl+F)당하는 순간 단 3초 만에 평문으로 까발려진다.

이 최악의 참사를 막기 위해 암호학자들은 주방에서 힌트를 얻었다. "고기에 후추(비밀번호)만 뿌리면 맛이 똑같잖아. 각 고기마다 소금(Salt)을 한 움큼씩 다르게 쳐서 구우면, 도둑이 훔쳐 먹었을 때 이게 무슨 고기인지 절대 유추할 수 없겠지!" 이것이 바로 **솔트(Salt)**의 등장이다.

📢 섹션 요약 비유: 시험(해킹)을 칠 때 해커가 '족보(레인보우 테이블)'를 훔쳐 왔습니다. 하지만 선생님이 학생마다 문제지 숫자를 엉뚱한 난수(솔트)로 다 바꿔버렸기 때문에, 해커가 훔쳐 온 족보는 완전히 쓸모없는 휴지조각이 되어버리는 완벽한 커닝 방지 시스템입니다.


Ⅱ. 솔트의 작동 원리와 방어 메커니즘

솔트를 치는 과정은 시스템 가입 시점과 로그인 시점으로 나뉘어 명확하게 작동한다.

1. 회원가입 시 (솔트 생성 및 암호화)

  • 유저 A가 비밀번호로 1234를 입력한다.
  • 서버는 이 순간 고도의 난수 생성기(CSPRNG)를 돌려 유저 A만을 위한 고유한 솔트값 Qx9@ 를 생성한다.
  • 서버는 비밀번호와 솔트를 합친다: 1234Qx9@
  • 이 합친 값을 SHA-256 믹서기에 넣고 돌린다 $\rightarrow$ 결과 해시값: Z9!P...
  • DB에는 솔트값 Qx9@ 와 해시값 Z9!P... 를 나란히 저장해 둔다.

2. 로그인 시 (비교 및 검증)

  • 유저 A가 다시 1234를 치고 로그인 버튼을 누른다.
  • 서버는 DB에서 유저 A의 고유 솔트 Qx9@ 를 꺼내와 방금 입력받은 1234 뒤에 똑같이 붙여 1234Qx9@ 를 만든다.
  • 이걸 해시 믹서기에 돌려보고, 그 결과가 DB에 저장된 해시값 Z9!P... 와 일치하면 문을 열어준다.
┌──────────────────────────────────────────────────────────────────────────────┐
│           동일한 비밀번호라도 솔트(Salt)에 의해 다르게 저장되는 원리 시각화  │
├──────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ [ 유저 A (비밀번호: "admin") ]                                               │
│   랜덤 배정된 솔트: "X9z!"                                                   │
│   합체: "adminX9z!" ──▶ (해싱) ──▶ 결과 해시: [ 3B2...9A ]                   │
│                                                                              │
│ [ 유저 B (비밀번호: "admin" - 우연히 똑같음!) ]                              │
│   랜덤 배정된 솔트: "pL4@"                                                   │
│   합체: "adminpL4@" ──▶ (해싱) ──▶ 결과 해시: [ 7F1...C2 ]                   │
│                                                                              │
│ ★ 해커의 절망 ☠️:                                                            │
│  DB를 훔쳐봤더니 A와 B의 해시값이 완전히 다름! "아, 얘네 둘이 똑같은         │
│  비밀번호를 쓰는지 유추할 수가 없네!"                                        │
│  게다가 훔쳐온 레인보우 테이블엔 "admin"은 있어도 "adminX9z!" 같은           │
│  괴상한 글자의 해시값은 없으므로 사전 공격이 완벽하게 차단됨!                │
└──────────────────────────────────────────────────────────────────────────────┘

[다이어그램 해설] 초보 개발자들이 가장 많이 오해하는 부분은 "솔트값(X9z!)이 DB에 평문으로 그냥 저장되어 있는데 해커한테 같이 털리면 위험한 거 아냐?"라는 것이다. 솔트의 목적은 비밀을 숨기는 것(기밀성)이 아니라, 레인보우 테이블을 무력화시키는 것이다. 해커가 솔트를 안다고 해도, 그 솔트(X9z!)가 적용된 자기만의 레인보우 테이블을 유저 100만 명에 대해 100만 번 처음부터 다시 만들어야 하므로, 해커의 시간과 돈이 말라버리게 된다.

  • 📢 섹션 요약 비유: 해커는 전 세계 모든 사람이 쓰는 흔한 열쇠 100만 개(레인보우 테이블)를 복사해서 들고 다녔습니다. 그런데 아파트 주인이 집집마다 열쇠 구멍의 모양을 제멋대로 깎아버렸습니다(솔트 추가). 해커가 그 구멍 모양(솔트)을 뻔히 눈으로 보고 알아도, 자기가 가진 100만 개의 열쇠를 그 구멍 모양에 맞춰 일일이 새로 깎아야 하니 결국 도둑질을 포기하게 됩니다.

Ⅲ. 실무 안티패턴 (절대 하면 안 되는 짓)

개발자들이 코딩을 할 때 솔트와 관련하여 저지르는 최악의 치명적 실수(Anti-Pattern)들이 있다.

  1. 글로벌 솔트(Global Salt / Pepper) 남용:
    • 귀찮다고 서버 소스코드에 SALT = "MySecretCompanySalt" 하나만 박아두고, 100만 명 회원 전체에게 똑같은 솔트를 친다.
    • 해커가 소스코드를 털어 글로벌 솔트를 알아내면? 이 회사를 위한 전용 레인보우 테이블 딱 1개만 새로 만들면 100만 명이 단번에 다 털린다. 솔트는 무조건 유저 1명마다 다 다르게 생성(Per-User Salt)해야 한다.
  2. 솔트 길이가 너무 짧음:
    • 솔트 길이를 고작 2글자(ab)로 한다면? 해커는 $26 \times 26 = 676$ 가지의 레인보우 테이블만 추가로 만들면 된다. 솔트는 최소 16바이트 이상의 강력하고 긴 난수(CSPRNG 사용)여야 한다.

Ⅳ. 솔트만으로는 부족한 시대 (키 스트레칭의 도입)

솔트 덕분에 레인보우 테이블이라는 거대한 꼼수는 막아냈다. 하지만 최근 10년 새 그래픽카드(GPU)의 성능이 미친 듯이 발전했다. 최고급 RTX 그래픽카드 8장을 묶어놓은 해킹 머신(Hashcat)은 1초에 1,000억 번의 해시를 돌린다.

해커가 유저 A의 해시 1개를 깨기 위해 "솔트(Qx9@)는 DB에서 봤으니 알지. 내 그래픽카드에 1234 + Qx9@, 1235 + Qx9@ ... 무한 반복해서 다 돌려봐!"라고 무식하게 힘으로 밀어붙이는 전수 조사(Brute Force)를 해버리면, 해시 함수가 너무 빠르기 때문에 고작 몇 분 만에 유저 A의 비번이 깨져버린다.

이 "해시 속도가 너무 빠르다"는 역설적 취약점을 막기 위해, 솔트와 함께 반드시 키 스트레칭(Key Stretching: 믹서기 10만 번 돌리기) 기법이 세트로 들어가야만 완벽한 현대 비밀번호 방어 아키텍처(Bcrypt 등)가 성립된다. [0109 문서 참조]


Ⅴ. 결론

"작은 난수 한 줌이 1TB짜리 해커의 무기를 고철로 만든다." 솔트(Salt)는 비밀번호 보호의 패러다임을 바꾼 가장 우아하고 간단한 보안 수학이다. 데이터베이스에 유저가 1,000만 명이라면 1,000만 개의 각기 다른 마법의 가루를 뿌려 해커가 지름길(레인보우 테이블)로 가는 것을 철벽처럼 막아세웠다. 당신의 백엔드 코드에 유저 전용 솔트를 생성하는 랜덤 함수가 없다면, 그 DB는 해커가 숟가락만 얹으면 밥상이 차려지는 무료 뷔페 식당과 같다.


📌 관련 개념 맵

  • 방어하는 대상 (공격 기법): 레인보우 테이블 공격 (Rainbow Table), 사전 공격 (Dictionary Attack)
  • 필수 짝꿍 기술: 키 스트레칭 (Key Stretching - Bcrypt, PBKDF2, Argon2)
  • 보안 사상: 확률적 암호화 (결정론적 암호를 무작위 난수로 비선형화함)
  • 안티패턴: 전역 솔트(Global Salt), 너무 짧은 솔트(Short Salt)

👶 어린이를 위한 3줄 비유 설명

  1. 학교 친구 10명이 모두 똑같은 자물쇠 비밀번호(1234)를 사서 썼더니, 도둑이 만능키 하나를 파서 10명 사물함을 다 털어버렸어요.
  2. 그래서 선생님이 친구들마다 비밀번호 끝에 아무도 모르는 마법의 스티커(솔트)를 몰래 붙여줬어요! 철수는 '1234+별', 영희는 '1234+달' 처럼요.
  3. 이제 도둑이 훔쳐 온 만능키(레인보우 테이블)를 아무리 찔러봐도 스티커 모양이 달라서 자물쇠가 열리지 않는 완벽한 방어막이 탄생한 거랍니다.