Cryptography isn't a single tool: it's three families of algorithms with completely different goals. Confusing them leads to using them the wrong way. A developer who stores passwords with AES instead of bcrypt, or who signs data with SHA-256 thinking that's enough, has a real security problem.
If you're starting from scratch, the introduction to the basic concepts is a good place to start before continuing here.
Symmetric cryptography: one key for everything
In symmetric cryptography, whoever encrypts and whoever decrypts use the same secret key. The dominant algorithm today is AES (Advanced Encryption Standard), adopted by NIST in 2001 as the successor to DES.
AES works on 128-bit blocks and supports 128, 192, or 256-bit keys. CBC (Cipher Block Chaining) mode was the de facto standard for years, but GCM (Galois/Counter Mode) has replaced it because it combines encryption and authentication in a single operation.
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
key = AESGCM.generate_key(bit_length=256)
aesgcm = AESGCM(key)
nonce = os.urandom(12)
plaintext = b"secret message"
ciphertext = aesgcm.encrypt(nonce, plaintext, None)
recovered = aesgcm.decrypt(nonce, ciphertext, None)
print(recovered) # b'secret message'
AES-GCM solves something CBC alone doesn't address: it guarantees the ciphertext hasn't been tampered with in transit. With plain CBC, an attacker can modify encrypted blocks in predictable ways without knowing the key.
The weak point of symmetric cryptography is key distribution: how do you share it securely with the other side? That's where asymmetric cryptography comes in.
Asymmetric cryptography: two keys, zero sharing
Asymmetric cryptography uses a pair of keys: a public one (freely distributable) and a private one (that never leaves the system). What you encrypt with the public key can only be decrypted with the private one.
RSA is the best-known algorithm, but in modern implementations Ed25519 is preferred for digital signatures: it's faster, produces shorter keys, and has a smaller attack surface than RSA with elliptic curves.
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
private_key = Ed25519PrivateKey.generate()
public_key = private_key.public_key()
message = b"this document is authentic"
signature = private_key.sign(message)
# anyone with the public key can verify
public_key.verify(signature, message) # raises InvalidSignature if tampered
In practice, asymmetric cryptography doesn't encrypt bulk data: it's too slow for that. TLS only uses it for the initial exchange of the session's symmetric key. After that handshake, everything travels over AES.
The SSH keys you use to authenticate to servers are exactly Ed25519 or RSA key pairs. How to generate them and set up passwordless authentication.
Hashing: one-way transformation
A hash isn't encryption. SHA-256 turns data of any size into a fixed 256-bit digest, and this process is irreversible by design: starting from the digest, you can't work backward to the original input.
import hashlib
digest = hashlib.sha256(b"any data").hexdigest()
print(digest)
# a2c3f4... always the same for the same input
SHA-256 is well suited for verifying file integrity, generating deterministic identifiers, or building structures like Merkle trees. For passwords, it's the wrong choice.
Passwords need a slow algorithm, with automatic salting and a configurable cost factor. bcrypt and Argon2 are designed for this: each call deliberately takes 100-300ms, making brute force computationally expensive even with modern hardware.
import bcrypt
password = b"user_password"
hashed = bcrypt.hashpw(password, bcrypt.gensalt(rounds=12))
# verification at login time
bcrypt.checkpw(password, hashed) # True
bcrypt.checkpw(b"wrong", hashed) # False
Using SHA-256 on passwords is a common mistake. Modern GPU farms crack billions of SHA-256 hashes per second. bcrypt with rounds=12 drops that to a few thousand, making brute force orders of magnitude more expensive.
Which one to use, and when
| Problem | Recommended algorithm | Notes |
|---|---|---|
| Encrypting data with a shared key | AES-GCM 256-bit | unique nonce for every operation |
| Key exchange, digital signature | Ed25519, RSA-OAEP | RSA minimum 2048-bit |
| File integrity, checksums | SHA-256, SHA-3 | avoid MD5 and SHA-1 |
| Password storage | bcrypt, Argon2id | never plain SHA-256 |
| Deriving a key from a password | Argon2id, PBKDF2 | to protect encrypted archives |
Modern encryption in apps like WhatsApp or Signal uses all three layers together: asymmetric for the initial key exchange, symmetric for the messages, hashing for signatures and integrity. How end-to-end encryption works in a real-world case.