저장 모양은 비슷한데 다루는 방법이 완전 다른 세 카테고리. 헷갈리는 게 민감한 줄도 모르고 뭘 흘리는 가장 쉬운 방법 중 하나.
| 카테고리 | 예시 | 민감도 | 다루기 |
|---|---|---|---|
| Identifier | username, email, user_id, IP 주소 | 낮음 (보통 public) | 평문으로 둬도 OK. 찾기 쉽게 index 만 걸어 |
| Credential | 비밀번호, PIN, biometric, hardware key | 높음 (정체성 증명) | 절대 평문으로 저장하지 마. 무조건 bcrypt/argon2/scrypt 로 hash |
| Secret | API key, JWT signing key, session token, OAuth client secret | 높음 (추가 증명 없이 접근 부여) | 저장할 땐 암호화하고, repo 에는 절대 두지 말고, 언제든 갈아끼울 수 있게 |
해싱 bright line
값이 사용자가 *아는* 거면 (비밀번호, PIN) 저장에서 원본을 절대 복원 가능하면 안 돼. 검증하는 유일한 방법은 input 을 hash 해서 hash 끼리 비교:
SHA256 말고 왜 bcrypt 냐고? SHA256 은 너무 빨라. GPU 가 초당 수십억 번 시도하거든. bcrypt 는 의도적으로 느려 (기본 cost 에서 hash 당 ~100ms), 도난된 hash 에서도 brute force 를 경제적으로 고통스럽게 만들어.
세 가지 실패 패턴
- Identifier 를 secret 으로 취급 — username 이나 ID 를 민감한 것처럼 숨김. 헛수고야. 어차피 로그에 다 남거든.
- Credential 을 identifier 로 저장 — DB 의 plain-text 비밀번호. 고전적 catastrophic 침해.
- Secret 을 repo 에 commit — 한 번 푸시되고 git 히스토리에 영원히 사는
.env의 API key. Track 9 에서 LLM-assisted 코딩이 이걸 어떻게 더 나쁘게 만드는지 다뤄.