저장 모양은 비슷한데 다루는 방법이 완전 다른 세 카테고리. 헷갈리는 게 민감한 줄도 모르고 뭘 흘리는 가장 쉬운 방법 중 하나.
| 카테고리 | 예시 | 민감도 | 다루기 |
|---|---|---|---|
| Identifier | username, email, user_id, IP 주소 | 낮음 (보통 public) | plain text OK; lookup 위해 index |
| Credential | 비밀번호, PIN, biometric, hardware key | 높음 (정체성 증명) | 절대 plain 저장 X; 항상 bcrypt/argon2/scrypt 로 hash |
| Secret | API key, JWT signing key, session token, OAuth client secret | 높음 (추가 증명 없이 접근 부여) | at-rest 암호화; repo 절대 X; 회전 가능해야 |
해싱 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 코딩이 이걸 어떻게 더 나쁘게 만드는지 다뤄.