Cryptography mistakes in application code are rarely about broken algorithms. They are about using the right tool for the wrong job: Base64 used as if it were encryption, a fast hash used for passwords, encryption used where a signature was needed. Each tool answers a different question, and once you know the question, the choice is usually obvious.
In part 4 several defences relied on TLS, signatures and hashes. This part explains what those building blocks are, how they differ and how TLS combines them. The next part shows a real bug that came from confusing two of them.
Five tools, five questions
| Tool | Question it answers | Needs a key? | Reversible? | Example |
|---|---|---|---|---|
| Encoding | How do I represent this data in another format? | No | Yes, by anyone | Base64, URL encoding, hex |
| Encryption | How do I keep this secret from people without the key? | Yes | Yes, with the key | AES-GCM, ChaCha20-Poly1305 |
| Hashing | How do I get a fixed fingerprint of this data? | No | No | SHA-256, SHA-3 |
| MAC (e.g. HMAC) | Was this data created by someone with the shared key, unchanged? | Yes (shared) | No | HMAC-SHA256 |
| Digital signature | Was this data created by the holder of a private key, unchanged? | Yes (key pair) | No | Ed25519, ECDSA, RSA-PSS |
The two most important rules fall straight out of the table: encoding is never security, and hashing is not encryption because nothing can be decrypted.
Encoding: a format, not a secret
Encoding converts data into another representation so it can travel through a system that expects a certain format. Base64 turns bytes into printable characters; URL encoding escapes characters that have meaning in a URL. Anyone can reverse it, without a key.
echo -n 'admin:s3cret' | base64
# YWRtaW46czNjcmV0
echo 'YWRtaW46czNjcmV0' | base64 -d
# admin:s3cret
That example is exactly what HTTP Basic authentication sends. It is safe only because TLS encrypts the connection underneath; the Base64 part adds no protection at all. If you see a value in a config file or database that "looks scrambled", decode it before assuming it is protected.
Encryption: secret with a key
Encryption transforms readable data (plaintext) into unreadable data (ciphertext) using a key, and only someone with the right key can reverse it. There are two families.
Symmetric encryption
The same key encrypts and decrypts. It is fast, and it is what protects bulk data: disks, database columns, backups and the data inside a TLS connection. The modern recommendation is an authenticated encryption mode such as AES-256-GCM or ChaCha20-Poly1305, which both hides the data and detects any tampering with the ciphertext.
# Python, using the "cryptography" package
import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
key = AESGCM.generate_key(bit_length=256) # store in a key manager, not in code
aead = AESGCM(key)
nonce = os.urandom(12) # unique per message, never reused with the same key
ciphertext = aead.encrypt(nonce, b"account 4417 balance 120.50", b"record:4417")
plaintext = aead.decrypt(nonce, ciphertext, b"record:4417")
The hard part of symmetric encryption is not the algorithm but the key: where it is stored, who can use it, how it is rotated. That is why cloud key management services exist.
Asymmetric encryption
Asymmetric cryptography uses a key pair: a public key that can be shared with anyone and a private key that never leaves its owner. Data encrypted with the public key can only be decrypted with the private key. It is much slower than symmetric encryption, so in practice it is used to agree on or protect a symmetric key, not to encrypt large data directly.
Hashing: a one-way fingerprint
A cryptographic hash function turns any input into a fixed-size output. The same input always gives the same output, a tiny change gives a completely different output, and you cannot get the input back from the output.
echo -n 'release-2.4.1.tar.gz contents' | sha256sum
Hashes are used to check file integrity, to deduplicate data and as building blocks for other tools. On their own they prove nothing about who created the data, because anyone can compute a hash. An attacker who replaces a download can also replace the published hash if both live on the same server.
Passwords need a special kind of hash
Fast hashes such as SHA-256 are the wrong tool for passwords. They are designed to be fast, so an attacker with a stolen database can try billions of guesses per second. Password storage needs a deliberately slow, salted function: Argon2id, scrypt or bcrypt. A salt is a random value stored with each hash so that identical passwords produce different hashes and precomputed tables are useless.
MACs and signatures: who sent it, and was it changed?
A message authentication code combines a hash function with a shared secret key. HMAC-SHA256 is the most common. Only someone with the key can produce a valid tag, so a correct tag proves the message came from a key holder and was not altered. Webhook signatures from payment providers are usually HMACs.
import hashlib, hmac
def verify_webhook(secret: bytes, body: bytes, received_tag: str) -> bool:
expected = hmac.new(secret, body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, received_tag) # constant-time comparison
A digital signature does the same with a key pair: the private key signs, and anyone with the public key can verify. Because verification does not require the secret, signatures also support non-repudiation. Software releases, container images and JWTs signed with RS256 or EdDSA use signatures.
How TLS puts it all together
A TLS 1.3 connection, simplified, uses almost every tool in this post:
- Key exchange: client and server use an asymmetric method (usually elliptic-curve Diffie-Hellman) to agree on a shared secret without ever sending it.
- Authentication: the server proves its identity with a certificate, which is its public key signed by a certificate authority. The client verifies that signature chain and that the name matches.
- Key derivation: both sides derive symmetric keys from the shared secret using an HMAC-based function.
- Data transfer: all application data is protected with symmetric authenticated encryption, such as AES-GCM.
Asymmetric cryptography solves the "how do we share a key with a stranger" problem, signatures solve "is this really the server", and symmetric encryption does the heavy lifting.
Common mistakes
| Mistake | Why it hurts | Do this instead |
|---|---|---|
| Base64 "encrypting" secrets in config | Anyone can decode it | Use a secrets manager or real encryption |
| SHA-256 or MD5 for passwords | Billions of guesses per second | Argon2id, scrypt or bcrypt |
| AES in ECB or unauthenticated CBC mode | Leaks patterns, allows tampering | AES-GCM or ChaCha20-Poly1305 |
| Reusing a nonce with the same key | Can expose plaintext in GCM | Random 96-bit nonce per message, or a counter |
Comparing MACs with == |
Timing differences leak information | Constant-time comparison function |
| Writing your own crypto | Subtle flaws are almost guaranteed | Use a well-reviewed library's high-level API |
Key takeaways
- Encoding changes format and protects nothing; encryption hides data from anyone without the key.
- Hashes are one-way fingerprints; passwords need slow, salted password-hashing functions.
- MACs prove a shared-key holder sent unchanged data; signatures prove it with a public key and support non-repudiation.
- Symmetric encryption protects bulk data, asymmetric cryptography exchanges keys and proves identity.
- TLS combines key exchange, signatures, key derivation and authenticated encryption in one handshake.
References
- NIST SP 800-175B Rev. 1, Guideline for Using Cryptographic Standards: https://csrc.nist.gov/pubs/sp/800/175/b/r1/final
- OWASP Cryptographic Storage Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html
- OWASP Password Storage Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
- RFC 8446, The Transport Layer Security (TLS) Protocol Version 1.3: https://www.rfc-editor.org/rfc/rfc8446
- pyca/cryptography documentation: https://cryptography.io/en/latest/
Comments
0No comments yet. Be the first to comment!
Sign In to post a comment.