C.W.K.
Stream
Lesson 03 of 06 · published

Identifier vs Credential vs Secret

~15 min · bcrypt, hashing, secrets

Level 0Greenhorn
0 XP0/53 lessons0/14 achievements
0/100 XP to next level100 XP to go0% complete

저장 모양은 비슷한데 다루는 방법이 완전 다른 세 카테고리. 헷갈리는 게 민감한 줄도 모르고 뭘 흘리는 가장 쉬운 방법 중 하나.

카테고리예시민감도다루기
Identifierusername, email, user_id, IP 주소낮음 (보통 public)plain text OK; lookup 위해 index
Credential비밀번호, PIN, biometric, hardware key높음 (정체성 증명)절대 plain 저장 X; 항상 bcrypt/argon2/scrypt 로 hash
SecretAPI 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 코딩이 이걸 어떻게 더 나쁘게 만드는지 다뤄.

Code

bcrypt 로 PIN hash (저장); input 재해싱으로 검증·python
import bcrypt

# 저장 — 절대 plain text 아님
pin = b"4729"
pin_hash = bcrypt.hashpw(pin, bcrypt.gensalt(rounds=12))
# DB 에 pin_hash 저장; 원본 PIN 은 절대 persist 안 됨

# 검증 — hash 끼리 비교, 절대 decrypt 아님
def verify(input_pin: str, stored_hash: bytes) -> bool:
    return bcrypt.checkpw(input_pin.encode(), stored_hash)

External links

Exercise

네 앱 DB 열어. 사용자 비밀번호 (또는 PIN) 저장하는 컬럼 찾아. bcrypt hash ($2b$ 로 시작) 인지 확인 — plain text 나 SHA256 이 아니라. 잘못됐으면 마이그레이션 작성: 각 row 읽고, 값을 bcrypt 로 hash, 다시 저장. 데이터 layer 가 plain credential 절대 보면 안 돼.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.