10장. 비밀번호는 어디에 저장되나
“해싱, 솔팅, 인증, 2FA”
이번 장에서 알게 될 것
- “비밀번호 찾기“를 하면 왜 원래 비밀번호를 안 알려주는지
- 서비스가 비밀번호를 저장하지 않으면서도 로그인을 확인하는 원리
- “123456“이 세상에서 가장 위험한 비밀번호인 이유
- 비밀번호가 사라지는 시대가 오고 있다는 것
치킨 주문 여정: 잠깐, 로그인부터
결제가 완료되었습니다. 그런데 이 결제를 하려면 먼저 배달앱에 로그인을 해야 했습니다. 비밀번호를 입력하고 로그인 버튼을 누르면, 그 비밀번호는 어디에 저장되어 있을까요?
비밀번호 찾기의 비밀
누구나 한 번쯤 비밀번호를 잊어본 적이 있을 겁니다. “비밀번호 찾기“를 누르면 어떤 일이 일어나나요?
원래 비밀번호를 알려주는 게 아니라, “새 비밀번호를 설정하세요” 라고 합니다.
왜 원래 비밀번호를 안 알려줄까요? 의지가 없는 것이 아닙니다. 못 알려주는 겁니다. 서비스도 여러분의 비밀번호를 모르기 때문입니다.
“내 비밀번호를 모른다고? 그러면 로그인할 때 어떻게 확인하지?”
여기서 해싱(Hashing) 이 등장합니다.
해싱: 고기를 다지면 되돌릴 수 없다
해싱은 데이터를 일정한 길이의 무작위처럼 보이는 값으로 변환하는 것입니다. 이 변환은 일방향입니다 — 변환할 수는 있지만 원래 값을 복원할 수 없습니다.
고기를 다지는 것을 생각하면 됩니다. 소고기를 다지기로 갈면 다진 고기가 나옵니다. 하지만 다진 고기에서 원래 소고기 덩어리를 복원할 수는 없습니다. 해싱도 마찬가지입니다.
[그림 10-1] 해싱의 일방향 특성
해싱 = 고기 다지기 (일방향):
입력 (비밀번호) 해시값 (저장되는 값)
───────────── ──────────────────
"chicken123" → "a7f3c2b9e1d4..."
"MyP@ssw0rd" → "5e2a8d1f9c6b..."
"123456" → "e10adc3949ba..."
✅ "chicken123" → "a7f3c2b9e1d4..." (항상 같은 결과)
❌ "a7f3c2b9e1d4..." → "chicken123" (역방향 불가능!)
서비스는 회원가입 시 비밀번호를 해싱해서 저장합니다. 원본 비밀번호가 아니라 해시값만 저장하는 겁니다.
로그인할 때는 어떻게 할까요?
[그림 10-2] 로그인 과정에서의 해시값 비교
로그인 과정:
1. 사용자가 비밀번호 입력: "chicken123"
2. 서버가 입력값을 해싱: "chicken123" → "a7f3c2b9e1d4..."
3. DB에 저장된 해시값과 비교: "a7f3c2b9e1d4..." == "a7f3c2b9e1d4..."
4. 일치 → 로그인 성공!
서버는 비밀번호 원본을 한 번도 보지 않았습니다.
해시값끼리 비교했을 뿐입니다.
그래서 “비밀번호 찾기“를 해도 원래 비밀번호를 알려줄 수 없는 겁니다. 서버에는 해시값만 있고, 해시값에서 원본을 복원하는 것은 불가능하니까요. 새 비밀번호를 설정하면 새 해시값을 저장하는 것이 전부입니다.
만약 어떤 서비스가 “비밀번호 찾기“를 했을 때 원래 비밀번호를 이메일로 보내준다면? 그 서비스는 비밀번호를 해싱하지 않고 원본 그대로 저장하고 있다는 뜻입니다. 매우 위험한 서비스입니다.
솔팅: 같은 재료에 소금을 다르게 넣기
해싱만으로 충분할까요? 문제가 있습니다.
같은 비밀번호는 항상 같은 해시값을 만듭니다. “123456“의 해시값은 언제나 “e10adc3949ba…“입니다. 해커가 미리 수백만 개의 비밀번호에 대한 해시값 표를 만들어 놓으면?
[그림 10-3] 레인보우 테이블 공격
레인보우 테이블[^1] (미리 계산한 해시값 표):
비밀번호 해시값
──────── ──────────────
"123456" → "e10adc3949ba..."
"password" → "5f4dcc3b5aa7..."
"qwerty" → "d8578edf8458..."
"iloveyou" → "f25a2fc72690..."
... → ... (수억 개)
해커가 DB에서 해시값을 탈취하면:
"e10adc3949ba..." → 표에서 찾기 → "123456" 발견!
이 표를 레인보우 테이블이라고 합니다. 자주 쓰이는 비밀번호의 해시값을 미리 계산해 놓은 것입니다. 해커가 해시값을 탈취하면, 이 표에서 역으로 비밀번호를 찾을 수 있습니다.
이 문제를 해결하는 것이 솔팅(Salting) 입니다. 해싱하기 전에 소금(Salt) — 무작위 문자열 — 을 비밀번호에 붙이는 것입니다.
[그림 10-4] 솔팅의 원리
솔팅 = 같은 재료 + 다른 소금 = 다른 맛:
사용자 A: 비밀번호 "123456" + 소금 "xK9m" → 해시 "7a3f1b..."
사용자 B: 비밀번호 "123456" + 소금 "pQ2r" → 해시 "c8e2d4..."
같은 비밀번호인데 해시값이 완전히 다릅니다!
→ 레인보우 테이블이 무용지물이 됩니다.
사용자마다 다른 소금을 사용하기 때문에, 같은 비밀번호라도 해시값이 전부 다릅니다. 레인보우 테이블은 소금이 없는 경우에만 작동하므로, 솔팅을 적용하면 무용지물이 됩니다.
소금은 비밀이 아닙니다. 해시값과 함께 DB에 저장됩니다. 소금의 목적은 비밀을 유지하는 것이 아니라, 같은 비밀번호가 같은 해시값을 만들지 못하게 하는 것입니다.
“123456“이 위험한 진짜 이유
매년 공개되는 “가장 많이 사용되는 비밀번호” 목록에서 “123456“은 수 년째 1위를 차지하고 있습니다.
[그림 10-7] 가장 많이 쓰이는 비밀번호 (2024)
세계에서 가장 많이 쓰이는 비밀번호 (2024):
1위 123456
2위 123456789
3위 password
4위 12345678
5위 qwerty
6위 1234567890
7위 111111
8위 1234567
9위 abc123
10위 password1
이 비밀번호들이 위험한 이유는 간단합니다. 해커가 로그인을 시도할 때, 이 목록의 비밀번호부터 넣어봅니다. 이것을 사전 공격(Dictionary Attack) 이라고 합니다. “123456“은 사전의 첫 페이지인 셈입니다.
솔팅과 해싱이 아무리 강력해도, 비밀번호 자체가 “123456“이면 해커가 “123456“을 직접 넣어보면 그만입니다. 자물쇠가 아무리 튼튼해도 열쇠가 “1111“이면 의미가 없는 것과 같습니다.
비밀번호의 길이와 복잡도에 따라 크래킹에 걸리는 시간은 극적으로 달라집니다.
[그림 10-8] 비밀번호 크래킹 예상 시간
비밀번호 크래킹 예상 시간 (최신 GPU 기준):
길이 숫자만 소문자 소문자+숫자+특수문자
──── ───── ────── ──────────────
6자리 즉시 즉시 5초
8자리 즉시 수 분 8시간
10자리 즉시 수 시간 5년
12자리 25초 수 주 3만 년
16자리 수 시간 수백만 년 수조 년
길이가 핵심입니다. 같은 소문자라도 6자리와 16자리는 하늘과 땅 차이입니다. 특수문자를 넣는 것보다 길이를 늘리는 것이 훨씬 효과적입니다.
2FA: 자물쇠를 두 개 달기
비밀번호만으로는 부족합니다. 비밀번호가 유출되면 끝이니까요. 그래서 등장한 것이 2FA(2-Factor Authentication)1 입니다.
2FA는 로그인할 때 두 가지 다른 종류의 인증을 요구합니다.
인증의 3가지 요소:
1. 내가 아는 것 (Something I know)
→ 비밀번호, PIN 번호
2. 내가 가진 것 (Something I have)
→ 휴대폰 (OTP[^3] 수신), 보안 키
3. 나 자신 (Something I am)
→ 지문, 얼굴, 홍채
2FA는 이 중 서로 다른 2가지를 조합합니다.
[그림 10-5] 2단계 인증(2FA) 로그인 과정
2FA 로그인:
1단계: 비밀번호 입력 ← "내가 아는 것"
┌───────────────────┐
│ 비밀번호: ******** │
│ [로그인] │
└───────────────────┘
2단계: 인증 코드 입력 ← "내가 가진 것"
┌───────────────────┐
│ 휴대폰으로 전송된 │
│ 6자리 코드: [______] │
│ 유효시간: 30초 │
└───────────────────┘
→ 비밀번호를 훔쳐도, 내 폰까지 훔치지 않으면 로그인 불가
은행 앱에서 비밀번호를 입력한 뒤 OTP 번호를 추가로 입력하는 것이 2FA입니다. 구글, 네이버 같은 서비스에서 로그인할 때 “새 기기에서 로그인을 시도합니다. 본인이 맞습니까?“라는 알림이 오는 것도 2FA의 일종입니다.
비밀번호는 유출될 수 있습니다. 하지만 비밀번호와 내 폰이 동시에 해커 손에 넘어갈 확률은 매우 낮습니다. 이것이 2FA의 핵심입니다.
패스키: 비밀번호의 종말?
비밀번호의 근본적인 문제는, 사람이 기억해야 한다는 것입니다. 기억하기 쉬운 비밀번호는 해킹도 쉽고, 어려운 비밀번호는 사람이 기억을 못 합니다. 서비스마다 다른 비밀번호를 쓰라고 하지만 현실적으로 지키기 어렵습니다.
그래서 등장한 것이 패스키(Passkey) 입니다.
패스키는 비밀번호를 아예 없애는 기술입니다. 대신 기기 자체가 인증합니다.
[그림 10-6] 패스키의 동작 원리
기존 로그인:
"비밀번호를 입력하세요" → 사용자가 타이핑 → 서버가 확인
패스키 로그인:
"로그인하려면 기기를 인증하세요" → 지문/얼굴 인식 → 완료
내부 동작:
1. 기기에 개인 키가 안전하게 저장됨
2. 서버에는 공개 키만 저장됨
3. 로그인 시 개인 키로 서명 → 서버가 공개 키로 검증
→ 비밀번호가 네트워크를 통해 전송되지 않음
→ 피싱 불가능 (가짜 사이트에 보낼 비밀번호가 없음)
9장에서 설명한 비대칭 암호가 여기서도 쓰입니다. 개인 키는 기기 밖으로 나가지 않고, 서버에는 공개 키만 있으므로 서버가 해킹당해도 인증 정보가 유출되지 않습니다.
애플, 구글, 마이크로소프트가 이미 패스키를 지원하고 있습니다. “비밀번호 없는 미래“가 멀지 않았습니다.
사건: 2012 LinkedIn 해킹 — 1억 개의 비밀번호가 유출되다
2012년, 비즈니스 전문 SNS LinkedIn에서 약 1억 1,700만 개의 사용자 비밀번호가 유출됩니다.2
문제는 LinkedIn이 비밀번호를 저장한 방식이었습니다.
[그림 10-9] LinkedIn 비밀번호 유출 사건
LinkedIn의 실수:
❌ SHA-1으로 해싱 (이미 취약하다고 알려진 알고리즘)
❌ 솔팅 없음 (같은 비밀번호 = 같은 해시값)
결과:
→ 해커가 레인보우 테이블로 대부분의 해시값을 역추적
→ 수천만 개의 비밀번호 원문이 복원됨
솔팅을 하지 않았기 때문에, “123456“을 쓴 모든 사용자의 해시값이 동일했습니다. 하나만 풀면 같은 비밀번호를 쓴 수십만 명의 계정이 동시에 뚫리는 겁니다.
더 심각한 문제는 비밀번호 재사용입니다. LinkedIn에서 유출된 비밀번호로 같은 사용자의 Gmail, 페이스북, 은행 계정에 로그인을 시도하는 것입니다. 이것을 크리덴셜 스터핑(Credential Stuffing)3 이라고 합니다. 서비스마다 다른 비밀번호를 사용해야 하는 이유가 바로 이것입니다.
이 사건 이후 업계 전체가 bcrypt, scrypt 같은 더 강력한 해싱 알고리즘과 솔팅을 표준으로 도입합니다.
알쓸신잡
-
비밀번호에 특수문자를 강제하는 게 정말 안전한가?: 2003년, 미국 국립표준기술연구소(NIST)의 빌 버는 “비밀번호에 대문자, 숫자, 특수문자를 포함하라“는 가이드라인을 발표합니다. 14년 후인 2017년, 그는 이 가이드라인을 후회한다고 밝힙니다. “P@ssw0rd!“처럼 예측 가능한 치환을 하게 만들 뿐이라는 겁니다. 현재 NIST는 “길고 외우기 쉬운 문장형 비밀번호” 를 권장합니다. “correcthorsebatterystaple“이 “P@55w0rD!“보다 더 안전합니다.
-
“나는 로봇이 아닙니다” CAPTCHA의 이중 목적: 웹사이트에서 “나는 로봇이 아닙니다“를 클릭하거나 “신호등이 있는 사진을 모두 고르세요” 같은 테스트를 요구합니다. 이것을 CAPTCHA4 라고 합니다. 봇(자동화 프로그램)의 접근을 차단하는 것이 주 목적이지만, 동시에 여러분의 응답이 AI 학습 데이터로 사용됩니다. 구글의 reCAPTCHA에서 왜곡된 글자를 읽거나 사진을 분류할 때, 여러분은 무료로 구글의 AI 학습에 기여하고 있는 겁니다.
-
세션 하이재킹에서 JWT까지: 6장에서 세션 토큰을 이야기했습니다. 초기 웹에서는 서버가 “이 사용자가 로그인했다“는 정보를 서버 메모리에 저장했습니다. 하지만 사용자가 수천만 명이 되면 서버의 부담이 커집니다. 그래서 현재는 JWT(JSON Web Token)5 라는 방식을 많이 사용합니다. 로그인 정보를 서버가 아닌 토큰 자체에 담아서 사용자에게 주는 것입니다. 서버는 토큰의 서명만 확인하면 되므로, 메모리에 세션을 저장할 필요가 없어집니다.
결제가 완료되었습니다. “주문이 접수되었습니다. 배달 예상 시간: 35분.” 화면에 배달원의 위치가 실시간으로 표시됩니다. 배달원이 우리 집을 향해 이동하는 파란 점. 이 점은 어떻게 내 폰에 실시간으로 표시되는 걸까요? 배달원의 위치는 어떻게 아는 걸까요?
-
2FA(2-Factor Authentication): 이중 인증. 로그인 시 서로 다른 두 가지 인증 수단을 요구하는 보안 방식. MFA(Multi-Factor Authentication, 다중 인증)로 확장되기도 한다. ↩
-
2012년 6월 유출, 2016년 전체 규모(1억 1,700만 건) 확인. — Krebs on Security ↩
-
크리덴셜 스터핑(Credential Stuffing): 한 서비스에서 유출된 아이디/비밀번호 조합을 다른 서비스에 자동으로 대입해보는 공격. 비밀번호를 재사용하는 사용자를 노린다. ↩
-
CAPTCHA(Completely Automated Public Turing test to tell Computers and Humans Apart): 컴퓨터와 사람을 구별하기 위한 자동화된 공개 튜링 테스트. ↩
-
JWT(JSON Web Token): 사용자 인증 정보를 JSON 형식으로 인코딩한 토큰. 서버가 세션을 저장하지 않아도 되는 무상태(stateless) 인증 방식. ↩