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

Identifiers vs Credentials vs Secrets

~15 min · bcrypt, hashing, secrets

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

Three categories that look similar in storage but require completely different handling. Mixing them up is one of the easiest ways to leak something you didn't realize was sensitive.

CategoryExamplesSensitivityHandling
IdentifierUsername, email, user_id, IP addressLow (often public)Plain text fine; index for lookup
CredentialPassword, PIN, biometric, hardware keyHigh (proves identity)Never store plain; always hash with bcrypt/argon2/scrypt
SecretAPI key, JWT signing key, session token, OAuth client secretHigh (grants access without further proof)Encrypted at rest; never in repo; rotate-able

The hashing bright line

If a value is something the user knows (password, PIN), you should never be able to recover the original from your storage. The only way to verify it is to hash the input and compare hashes:

Why bcrypt and not SHA256? Because SHA256 is too fast: a GPU can try billions per second. Bcrypt is intentionally slow (~100ms per hash at default cost), making brute force economically painful even on stolen hashes.

The three failure patterns

  • Identifier treated as secret — Hiding usernames or IDs as if they're sensitive. Wastes effort; they leak via logs anyway.
  • Credential stored as identifier — Plain-text passwords in DB. The classic catastrophic breach.
  • Secret committed to repo — API key in .env that got pushed once and lives forever in git history. Track 9 covers how LLM-assisted coding makes this worse.

Code

Hash a PIN with bcrypt (storage); verify by re-hashing input·python
import bcrypt

# Storage — never plain text
pin = b"4729"
pin_hash = bcrypt.hashpw(pin, bcrypt.gensalt(rounds=12))
# Save pin_hash in DB; the original PIN never persists

# Verification — compare hashes, never decrypt
def verify(input_pin: str, stored_hash: bytes) -> bool:
    return bcrypt.checkpw(input_pin.encode(), stored_hash)

External links

Exercise

Open your app's database. Find the column that stores user passwords (or PINs). Verify it stores bcrypt hashes (they start with $2b$), not plain text or SHA256. If wrong, write the migration: read each row, hash the value with bcrypt, write back. The data layer should never see plain credentials.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.