Explain the difference between encoding, hashing and encryption, and use Python's hashlib to check integrity and store passwords safely.
Why it matters
Cryptography sits under almost every control you have met so far: TLS, password storage, software signatures, disk encryption, MFA codes. You do not need to invent algorithms (and you never should), but you do need to pick the right tool. The most common beginner mistake is confusing encoding, hashing and encryption, which do very different jobs.
Three different tools
| Tool | Reversible? | Needs a key? | Purpose | Examples |
|---|---|---|---|---|
| Encoding | Yes, by anyone | No | Represent data in another format | Base64, URL encoding, hex |
| Hashing | No (one-way) | No | Fingerprint data to check integrity; store passwords | SHA-256, SHA-3; bcrypt, scrypt, Argon2, PBKDF2 for passwords |
| Encryption | Yes, with the key | Yes | Keep data confidential | AES (symmetric), RSA and elliptic-curve (asymmetric) |
Base64 is not security. cGFzc3dvcmQ= decodes to password in one command. If you see "encrypted" data that ends in = and no key was involved, it is probably just encoded.
Hashing
A cryptographic hash function turns any input into a fixed-size digest. Good hash functions have three properties:
- Deterministic: the same input always gives the same digest.
- One-way: you cannot work back from the digest to the input.
- Collision-resistant: it is infeasible to find two inputs with the same digest. A one-character change produces a completely different digest (the avalanche effect).
In Python:
import hashlib
print(hashlib.sha256(b"hello").hexdigest())
# 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
print(hashlib.sha256(b"hellp").hexdigest())
# a completely different 64-character digest
Integrity checks are the classic use. A download page publishes the SHA-256 of a file; after downloading, you hash your copy and compare. If they match, the file was not corrupted or swapped (as long as you got the published hash from a trusted place).
MD5 and SHA-1 are broken for collisions: researchers can create two different files with the same digest. Do not use them for security; use SHA-256 or stronger.
Hashing passwords
Servers should never store passwords, even encrypted. They store a password hash and compare hashes at login. Plain SHA-256 is the wrong tool for this, for two reasons:
- It is fast. Attackers with GPUs can try billions of guesses per second against a stolen hash.
- Identical passwords give identical hashes, so precomputed tables of common-password hashes work instantly.
The fix is a slow, salted password hashing function: bcrypt, scrypt, Argon2 or PBKDF2.
- A salt is random data, unique per user, stored next to the hash. Two users with the same password get different hashes, and precomputed tables become useless.
- A work factor (iterations or memory cost) makes each guess expensive. Logging in takes a few milliseconds more; guessing a million passwords takes far longer.
Python's standard library includes PBKDF2:
import hashlib, hmac, os
salt = os.urandom(16)
digest = hashlib.pbkdf2_hmac("sha256", b"correct horse", salt, 600_000)
# At login: recompute with the stored salt and compare in constant time
attempt = hashlib.pbkdf2_hmac("sha256", b"correct horse", salt, 600_000)
print(hmac.compare_digest(digest, attempt)) # True
hmac.compare_digest compares in constant time, so an attacker cannot learn how many leading bytes matched by timing the response. In production, use a maintained library (for example bcrypt or argon2-cffi) or your framework's built-in password hasher, with the work factor it recommends today.
Encryption
Encryption keeps data secret from anyone without the key.
- Symmetric encryption uses one shared key to encrypt and decrypt. AES is the standard. It is fast, so it is used for bulk data: disk encryption, database fields, the body of a TLS session. The hard part is sharing and protecting the key.
- Asymmetric encryption uses a key pair: a public key anyone may have, and a private key only the owner holds. Data encrypted with the public key can only be decrypted with the private key. It is slower, so it is used to exchange symmetric keys and to create digital signatures: the owner signs with the private key, and anyone can verify with the public key. That is how TLS certificates and software updates are verified.
TLS combines both: asymmetric cryptography during the handshake to authenticate the server and agree a key, then fast symmetric encryption for the data.
Key management is where real systems fail: keys committed to Git, the same key everywhere, keys never rotated. Use a key management service or secrets manager, restrict who can use each key, and rotate them.
Common mistakes
- "We encrypt passwords." Passwords should be hashed with a slow, salted function, not encrypted (encryption can be reversed by anyone who gets the key).
- Using MD5 or SHA-1 for anything security-related.
- Rolling your own crypto, including "clever" combinations of standard pieces. Use well-reviewed libraries with safe defaults.
- Comparing secrets with
==in authentication code instead of a constant-time comparison. - Calling Base64 encryption.
Quick recap
- Encoding changes format (no secrecy), hashing fingerprints one way, encryption hides data and can be reversed with a key.
- Use SHA-256 or stronger for integrity; never MD5 or SHA-1 for security.
- Store passwords with a slow, salted function (bcrypt, scrypt, Argon2, PBKDF2) and compare in constant time.
- Symmetric encryption (AES) for bulk data, asymmetric for key exchange and signatures; protect and rotate keys.
Try it
Open the Do tab to verify a file's integrity and hash passwords with Python's hashlib.