What Is Hashing, and How Is It Different from Encryption?
A complete, beginner‑to‑production walkthrough of hashing and encryption — what they really are, how they work internally, where each one belongs in real systems, and the mistakes that quietly cost engineers dearly in interviews and in production.
Introduction & History
If you have ever set a password on a website, downloaded a file and seen a strange string labelled “SHA‑256 checksum” sitting next to it, or used a messaging app that says “your messages are end‑to‑end encrypted,” you have already brushed up against two of the most important ideas in computer science: hashing and encryption. Most beginners — and, honestly, more than a few experienced engineers — use these two words as if they were interchangeable. They are not. They solve different problems, they are built on different mathematics, and picking the wrong one for a job can quietly create a security hole that sits in production for years before anyone notices.
This tutorial exists to fix that confusion permanently. By the end, you will be able to explain — in an interview, in a design review, or to a junior engineer on your team — exactly what hashing is, exactly what encryption is, why they are fundamentally different tools, and which one you should reach for in a dozen common real‑world scenarios: storing passwords, verifying file integrity, building a CDN cache key, encrypting a database column, signing a JWT, or sharding data across a distributed cache.
A short history of hashing
The idea of a “hash function” predates modern cryptography. In the 1950s, computer scientists working on early data‑processing systems needed a fast way to look up records in memory without scanning an entire list one item at a time. Hans Peter Luhn, an IBM researcher, is widely credited with formalising the idea of hashing for information retrieval around 1953 — the goal was purely about speed: take a piece of data (like a customer’s last name), run it through a function, and get back a small number that tells you which “bucket” to look in. This is the ancestor of what we now call a hash table, and it had nothing to do with security at all.
Cryptographic hashing — hash functions specifically designed to resist tampering and reverse‑engineering — came later, growing out of the same research community that gave us modern cryptography in the 1970s. MD5 (1992), SHA‑1 (1995), and the SHA‑2 family (2001) turned “hashing” into a security tool, not just a performance trick. Today, when engineers say “hashing” in a security context, they almost always mean a cryptographic hash function, and that is the sense we will focus on in this tutorial, while still covering non‑cryptographic hashing where it is relevant (like hash tables and load balancing).
A short history of encryption
Encryption is far older than computing itself. Julius Caesar reportedly used a simple substitution cipher — shifting each letter of the alphabet by three positions — to send military orders that his enemies could not read even if the messenger was captured. That is the essence of encryption: transforming readable information (“plaintext”) into unreadable information (“ciphertext”) using a secret, and being able to reverse that transformation using the same (or a related) secret.
Modern encryption really took shape in the 20th century. The Enigma machine used by Germany in World War II, and the Allied effort to break it (led in part by Alan Turing), is probably the most famous chapter in encryption history. In the computing era, the Data Encryption Standard (DES) was published by the US government in 1977, followed decades later by the Advanced Encryption Standard (AES) in 2001, which remains the world’s most widely used symmetric encryption algorithm today. In parallel, a completely different idea emerged in the 1970s — public‑key (asymmetric) encryption, pioneered by Whitfield Diffie, Martin Hellman, and separately by Ron Rivest, Adi Shamir, and Leonard Adleman (who gave us RSA) — which solved a problem symmetric encryption could not: how do two people who have never met agree on a secret key over an insecure channel?
Hash tables for data lookup
Luhn and IBM formalise hashing purely as a data‑retrieval trick — no security angle at all, just speed.
DES + public‑key cryptography
DES is standardised for symmetric encryption; Diffie–Hellman and RSA invent public‑key cryptography, solving the “secret‑sharing over an open channel” problem.
MD5 and SHA‑1
Cryptographic hash functions become mainstream, used for message digests, checksums and early digital signatures.
AES and SHA‑2 arrive
AES is adopted as the modern symmetric encryption standard; the SHA‑2 family is published as MD5 and SHA‑1 start to show their age.
SHA‑3 finalised, Argon2 wins
SHA‑3 is finalised as a fresh design; bcrypt, scrypt and Argon2 are widely adopted specifically for password hashing.
Both fields matured independently for decades before software engineers started combining them into the systems we build today: TLS connections that use asymmetric encryption to exchange a symmetric key, then symmetric encryption to protect the actual traffic, with hashing woven throughout for integrity checks, digital signatures, and password storage.
The Problem & Motivation
To understand why we need two different tools instead of one, it helps to ask: what problem is each one actually solving?
The problem hashing solves
Imagine you run an e‑commerce website with ten million registered users. You need to verify a password every time someone logs in — but you must never store the actual password anywhere, because if your database is ever breached (and eventually, statistically, it will be), you do not want to hand attackers a plaintext list of everyone’s passwords. You need a way to check “does this input match what the user originally set?” without ever being able to retrieve the original value yourself, even if you wanted to.
You also need a fast way to verify that a 4GB file you downloaded was not corrupted or tampered with in transit, without comparing it byte‑by‑byte to the original. And you need a way to organise a huge dataset — like a hash table storing a million user records — so that looking up any single record takes constant time, not a linear scan.
Think of a hash function like a paper shredder that always shreds the same document into the exact same pile of confetti, every single time. If you have two identical documents, you get two identical piles of confetti. But looking at a pile of confetti, there is no way to reconstruct the original document — the process only goes one direction.
The problem encryption solves
Now imagine a different scenario. You are building a healthcare app that stores patients’ medical records. Doctors and the patients themselves need to read this data later — you cannot shred it into confetti, because someone legitimately needs to recover the original content. But you also cannot store it as plain, readable text, because a database breach would expose sensitive medical history. You need a way to lock the data with a key, such that only someone holding the correct key can unlock and read it again.
Similarly, when your browser talks to your bank’s website, the data flowing over the network (your account balance, your transactions) needs to be unreadable to anyone eavesdropping on the connection — but your bank’s server on the other end absolutely needs to read the real data to process your request.
Think of encryption like a locked box with a specific key. You put your valuables (plaintext) in the box, lock it (encrypt), and mail it. Anyone who intercepts the box sees only a sealed container. But the intended recipient, who has the matching key, can unlock it (decrypt) and get your valuables back exactly as they were.
Why one tool cannot replace the other
This is the crux of the whole topic: hashing is one‑way by design; encryption is two‑way by design. If you use encryption where you needed hashing (e.g., storing passwords with reversible encryption instead of a one‑way hash), you have created a system where a single leaked key exposes every password in plaintext. If you use hashing where you needed encryption (e.g., trying to “hash” a credit card number so you can later show it to the user), you have created a system that mathematically cannot ever recover the original data — the feature is simply broken.
| Question you’re asking | Tool you need |
|---|---|
| “Does this input match a known value, without me ever storing the original?” | Hashing |
| “I need to send/store this data and get the exact original back later.” | Encryption |
| “I need to detect if this file/message was tampered with.” | Hashing (checksums, HMAC) |
| “I need to keep this data secret from anyone without the key.” | Encryption |
| “I need to distribute data evenly across servers/buckets.” | Hashing (non‑cryptographic) |
Core Concepts
Both hashing and encryption transform data into something that looks unreadable — and that surface‑level similarity is exactly why people mix them up. Under the hood they solve two very different problems with two very different sets of mathematical guarantees.
3.1 What is a hash function?
A hash function is a mathematical function that takes an input of any size (a single character, a password, or a 10GB video file) and produces an output of a fixed size, called a hash value, digest, or simply a hash. For example, SHA‑256 always produces a 256‑bit (32‑byte) output, no matter whether you feed it the word “hi” or the entire text of “War and Peace.”
A good cryptographic hash function has these essential properties:
- Deterministic: the same input always produces the same output. Every time.
- Fast to compute: given any input, you can compute its hash quickly.
- Pre‑image resistant (one‑way): given a hash output, it should be computationally infeasible to figure out what input produced it.
- Collision resistant: it should be extremely difficult to find two different inputs that produce the same output (a “collision”). It should also be infeasible to find a second input that collides with a given first input (this is called “second pre‑image resistance”).
- Avalanche effect: a tiny change in the input (even flipping a single bit) should produce a completely different, unrecognisable output.
Hashing the string "password123" with SHA‑256 always gives you ef92b778bafe771e89245b89ecbc08a... (64 hex characters). Change it to "password124" and the output is completely different, sharing no visible resemblance to the first hash — that’s the avalanche effect in action.
3.2 What is encryption?
Encryption is a reversible transformation of data (plaintext) into an unreadable form (ciphertext) using an algorithm and a key. Unlike hashing, encryption always comes with a matching decryption operation that reverses the process, given the correct key.
There are two broad families of encryption:
Same key both sides AES
The same key is used to encrypt and decrypt. It’s fast and efficient for large amounts of data. Examples: AES, ChaCha20, the old DES. Analogy: a single physical key that both locks and unlocks your front door.
Public / private key pair RSA
A mathematically linked pair of keys — a public key (safe to share with anyone) and a private key (kept secret) — where data encrypted with one can only be decrypted with the other. Examples: RSA, ECC. Analogy: a mailbox with a slot anyone can drop letters into (public key), but only you have the key to open the mailbox and read them (private key).
3.3 Side‑by‑side: hashing vs encryption
| Aspect | Hashing | Encryption |
|---|---|---|
| Direction | One‑way (irreversible) | Two‑way (reversible with key) |
| Purpose | Integrity, verification, indexing, fingerprinting | Confidentiality — keeping data secret |
| Output size | Fixed length regardless of input size | Typically proportional to input size (plus padding/overhead) |
| Uses a key? | No (except keyed variants like HMAC) | Yes, always |
| Can original data be recovered? | No, never — not even by the system that created the hash | Yes, by anyone holding the correct key |
| Typical algorithms | SHA‑256, SHA‑3, bcrypt, Argon2, MD5 (legacy) | AES, ChaCha20 (symmetric); RSA, ECC (asymmetric) |
| Typical use cases | Password storage, checksums, digital signatures, hash tables, blockchain | Data‑at‑rest encryption, TLS/HTTPS, encrypted messaging, VPNs |
Hashing answers “is this the same as before?” Encryption answers “who is allowed to see this?” If your feature needs to later show the original value to a legitimate user, you need encryption. If your feature only ever needs to compare values, you need hashing.
3.4 Where confusion usually creeps in
Three areas cause almost all real‑world confusion between hashing and encryption:
- Encoding is neither hashing nor encryption. Base64 is a common source of confusion — it looks scrambled, but it is fully reversible with no key at all, and is meant for safely transmitting binary data as text, not for security.
- “Encrypted passwords” is almost always the wrong phrase. Properly built systems hash passwords (with a slow, salted algorithm) — they do not encrypt them, because encryption implies someone, somewhere, can recover the original.
- Not all hashing is cryptographic. Java’s
hashCode(), used forHashMapbucketing, is a non‑cryptographic hash — fast, but trivially reversible/predictable, and totally unsuitable for security purposes.
Architecture & Components
To use hashing and encryption correctly in a real system, you need to understand the components that surround the core algorithm — because the raw algorithm alone (SHA‑256, AES) is rarely enough on its own.
4.1 Components of a production hashing system (password storage)
Hash algorithm
A purpose‑built, slow password‑hashing algorithm — bcrypt, scrypt, or Argon2 (never a fast general‑purpose hash like SHA‑256 alone).
Salt
A random value generated per user and stored alongside the hash. It ensures two users with the same password get completely different hash outputs, defeating precomputed “rainbow table” attacks.
Work factor / cost
A tunable “difficulty” setting that controls how many rounds of computation the algorithm performs, deliberately slowing it down so brute‑force attacks become expensive.
Pepper (optional)
An application‑wide secret (not stored in the database, often in a secrets manager or environment variable) added to the input before hashing, providing defense‑in‑depth if the database alone is compromised.
4.2 Components of a production encryption system
Algorithm & mode
e.g., AES‑256 in GCM mode (an “authenticated encryption” mode that also verifies integrity).
Key
The secret used to encrypt/decrypt. Never hard‑coded; managed externally.
Key Management System
A dedicated service (AWS KMS, Google Cloud KMS, HashiCorp Vault) that generates, stores, rotates, and audits access to encryption keys, so application code never directly touches raw key material longer than necessary.
IV / Nonce
An Initialization Vector is a random value used once per encryption operation so that encrypting the same plaintext twice with the same key produces different ciphertexts.
Authentication tag
In authenticated encryption modes (like GCM), a tag is produced that lets the decryptor verify the ciphertext was not tampered with.
4.3 Where each component lives in a typical architecture
In a real multi‑tier application, hashing and encryption responsibilities are usually distributed like this:
| Layer | Hashing responsibility | Encryption responsibility |
|---|---|---|
| Client / Browser | — | TLS handshake initiation (HTTPS) |
| Load Balancer / Gateway | — | TLS termination (decrypts inbound traffic) |
| Application Service | Password hashing, HMAC signature checks, cache key generation | Field‑level encryption of sensitive columns before writing to DB |
| Database | Stores password hashes as opaque strings | Transparent Data Encryption (TDE) at rest |
| Storage / Object Store | Content checksums (e.g., S3 ETag/SHA‑256) | Server‑side encryption (SSE‑KMS, SSE‑S3) |
Internal Working
This is where the “magic” underneath both tools becomes concrete: bit manipulations, block ciphers, key schedules, and clever mathematical asymmetries.
5.1 How a cryptographic hash function actually works (SHA‑256, simplified)
SHA‑256 belongs to a family of hash functions built on the Merkle–Damgård construction. Conceptually, it works like this:
- Padding: the input message is padded so its length is a multiple of 512 bits, with the original message length encoded at the end.
- Splitting into blocks: the padded message is broken into 512‑bit chunks.
- Compression function: each 512‑bit block is processed through 64 rounds of bitwise operations (AND, OR, XOR, bit rotation) mixed with round constants, updating an internal 256‑bit “state.”
- Chaining: the output state from processing one block feeds in as the input to process the next block — this is why it’s called “chaining.”
- Final digest: after the last block is processed, the final 256‑bit internal state is the hash output.
Because every block’s processing depends on the previous block’s output, and each round mixes bits extensively, a single flipped bit anywhere in the input cascades into a completely different final state — the avalanche effect mentioned earlier.
5.2 Why password hashing uses special algorithms (bcrypt, Argon2)
SHA‑256 is fast — modern hardware can compute billions of SHA‑256 hashes per second. That is great for checksums, but terrible for password storage: if an attacker steals your database of SHA‑256 password hashes, they can try billions of password guesses per second against it (a “brute‑force” or “dictionary” attack).
bcrypt and Argon2 are deliberately slow and memory‑hard. Argon2, the winner of the 2015 Password Hashing Competition, requires large amounts of memory to compute, which makes it expensive to parallelise on GPUs or custom ASICs — hardware attackers love to use to brute‑force fast hashes. This “slowness” is a feature, not a bug: it might take your server 100 milliseconds to hash one password at login (imperceptible to a real user), but it means an attacker can only attempt a few hundred guesses per second per GPU instead of billions.
“I’ll just hash passwords twice with SHA‑256 to make it more secure.” This does not work the way people think — it barely slows down an attacker and doesn’t add the memory‑hardness that actually defeats GPU cracking. Always use a purpose‑built algorithm (bcrypt, scrypt, Argon2, or PBKDF2 with a high iteration count) instead of layering general‑purpose hashes.
5.3 How symmetric encryption works internally (AES, simplified)
AES (Advanced Encryption Standard) is a block cipher — it encrypts data in fixed‑size blocks (128 bits at a time for AES). Internally, for AES‑256 (a 256‑bit key), the algorithm runs the plaintext block through 14 rounds of transformation, where each round performs:
- SubBytes: each byte is replaced using a lookup table (S‑box) that introduces non‑linearity.
- ShiftRows: rows of the internal state matrix are cyclically shifted.
- MixColumns: columns are mathematically mixed together (matrix multiplication in a finite field).
- AddRoundKey: the block is XORed with a “round key” derived from the original encryption key via a key‑schedule algorithm.
Because a single 128‑bit block cipher call only encrypts 16 bytes, real messages (which are usually much larger) need a mode of operation to chain multiple block operations together. This is where GCM (Galois/Counter Mode) comes in — the modern recommended choice, because it provides both confidentiality (encryption) and integrity (via an authentication tag) in one pass, unlike older modes like ECB (insecure — identical plaintext blocks produce identical ciphertext blocks, leaking patterns) or CBC (secure for confidentiality but needs a separate integrity mechanism like HMAC).
5.4 How asymmetric encryption works internally (RSA, simplified)
RSA relies on a mathematical fact that is easy to compute in one direction and extremely hard to reverse: it is easy to multiply two large prime numbers together, but extremely hard to factor their product back into the original primes if the numbers are large enough (thousands of bits).
- Generate two large random primes, p and q.
- Compute n = p × q — this becomes part of both the public and private key.
- Derive a public exponent e and a private exponent d mathematically related to p, q, and n using Euler’s totient function.
- The public key is (n, e); the private key is (n, d).
- Encryption: ciphertext = plaintexte mod n. Decryption: plaintext = ciphertextd mod n.
Because factoring n back into p and q is computationally infeasible for sufficiently large keys (2048 bits or more), an attacker who only has the public key cannot derive the private key. In practice, RSA is slow and typically used only to encrypt a small symmetric key (see “hybrid encryption” below), not entire messages.
5.5 Hybrid encryption: how TLS actually combines both
Real systems like HTTPS/TLS use asymmetric and symmetric encryption together, because asymmetric encryption is secure for key exchange but too slow for bulk data, while symmetric encryption is fast but requires a securely shared key:
Data Flow & Lifecycle
Understanding the algorithms only gets you halfway. The lifecycle — what happens on write, on read, on rotation, on breach — is what determines whether a design actually survives production.
6.1 Lifecycle of a hashed password
- Registration: user submits plaintext password over HTTPS. Server generates a random salt, runs password + salt through Argon2/bcrypt, discards the plaintext password from memory, stores only the resulting hash + salt (often combined into one encoded string) in the database.
- Login: user submits plaintext password again. Server retrieves the stored salt for that user, re‑runs the same hash function with the submitted password + retrieved salt, and compares the newly computed hash to the stored hash using a constant‑time comparison (to prevent timing attacks).
- Password change / reset: a brand‑new salt is generated, and the whole hash is recomputed and replaces the old one — the old hash is never “updated,” it is fully replaced.
- Breach scenario: if the database leaks, the attacker only has salts and hashes — never plaintext passwords — and must brute‑force each one individually (slowly, thanks to the algorithm’s cost factor).
6.2 Lifecycle of encrypted data (e.g., a sensitive database field)
- Write path: application receives sensitive data (e.g., a bank account number). It requests a data encryption key from a KMS, generates a fresh IV, encrypts the field using AES‑GCM, and stores ciphertext + IV + auth tag in the database. The plaintext is never persisted.
- Read path: application fetches ciphertext from the database, requests the decryption key from KMS (subject to access‑control policies and audit logging), verifies the auth tag, and decrypts back to plaintext only in memory, only for the duration needed.
- Key rotation: periodically, KMS issues a new key version. Newly written data uses the new key; existing data can be re‑encrypted in the background (“key rotation”) without downtime, often tracked via a key‑version identifier stored alongside the ciphertext.
- Revocation: if a key is suspected compromised, access can be revoked at the KMS level immediately, cutting off decryption for anyone without a valid, current key — a capability hashing has no equivalent for, since there is no “key” to revoke.
Notice that hashing has no “read path” that recovers the original — by design. Encryption’s entire lifecycle is built around controlled, auditable recovery of the original data. This single difference drives almost every architectural decision downstream.
Pros, Cons & Trade‑offs
Every cryptographic choice buys some safety at the price of some flexibility. Being honest about both sides of that ledger is what separates a security engineer from a security enthusiast.
7.1 Hashing: pros and cons
Pros
- Original data can never leak from the hash alone, even to the system that computed it
- Fixed‑size output regardless of input size — great for indexing and comparison
- No key management burden
- Fast to verify equality (compare two fixed‑length strings)
- Naturally suited to tamper detection (any change alters the hash completely)
Cons
- Cannot recover original data — unsuitable when you need the original back
- Vulnerable to brute‑force / dictionary attacks if unsalted or if using a fast algorithm on low‑entropy input (like passwords)
- Collisions are theoretically possible (though astronomically unlikely for modern algorithms like SHA‑256)
- Legacy algorithms (MD5, SHA‑1) are cryptographically broken and must be avoided for security purposes
7.2 Encryption: pros and cons
Pros
- Original data can always be recovered by an authorised party
- Supports secure communication between parties who choose to trust each other
- Asymmetric encryption solves secure key exchange over insecure channels
- Modern authenticated modes (AES‑GCM) provide confidentiality and integrity together
Cons
- Requires careful key management — a lost key means permanently lost data; a leaked key means fully exposed data
- Symmetric key distribution is a hard problem on its own (solved via asymmetric encryption or key‑exchange protocols)
- Asymmetric encryption is computationally expensive — unsuitable for bulk data alone
- Adds operational complexity: rotation, revocation, access auditing, compliance overhead
7.3 Trade‑off table: choosing the right tool
| Scenario | Right choice | Why |
|---|---|---|
| Storing user passwords | Hashing (bcrypt/Argon2 + salt) | You only ever need to verify, never recover, the original |
| Storing credit card numbers for later display / refunds | Encryption (or better: tokenisation via a PCI‑compliant vault) | The original value must be recoverable for legitimate operations |
| Verifying a downloaded file wasn’t corrupted | Hashing (SHA‑256 checksum) | You just need to detect any difference from the original |
| Protecting data in a database from unauthorised reads | Encryption | Authorised readers must recover the plaintext |
| Building a distributed cache key from a request URL | Hashing (non‑cryptographic, e.g., MurmurHash) | Speed and even distribution matter more than cryptographic strength |
| Signing an API request to prove it came from a trusted source | Hashing (HMAC, keyed hash) | Combines hashing’s tamper‑evidence with a shared secret for authentication |
Performance & Scalability
Cryptographic operations are almost never the raw bottleneck of a modern web service — but a wrongly‑tuned password hash function absolutely can be. Understanding the performance shape of each primitive is what turns a secure design into a secure and scalable one.
8.1 Performance characteristics of hashing
Non‑cryptographic hash functions (used in HashMap, load balancers, consistent hashing rings) are designed to be extremely fast — often just a handful of CPU cycles per byte, because their job is pure data distribution, not security. Cryptographic hash functions like SHA‑256 are slower but still fast enough to hash gigabytes per second on modern hardware — fine for checksums and signatures, but, as discussed, deliberately too fast for password storage.
Password‑hashing algorithms (bcrypt, Argon2) are the exception: they are intentionally slow (tens to hundreds of milliseconds per hash) and tunable via a cost factor, trading a small, acceptable latency hit at login time for a massive increase in attacker cost.
8.2 Performance characteristics of encryption
Symmetric encryption (AES) is fast — modern CPUs have dedicated AES‑NI instructions that can encrypt multiple gigabytes per second per core, making it practical to encrypt bulk data (files, database columns, network traffic) with negligible overhead. Asymmetric encryption (RSA, ECC) is orders of magnitude slower and is deliberately used sparingly — typically only for key exchange or digital signatures, never for encrypting large payloads directly.
8.3 Scalability considerations at the system level
At scale, both hashing and encryption introduce distinct bottlenecks that architects need to plan for:
- Hashing at scale: password hashing’s deliberate slowness becomes a real capacity‑planning input — if Argon2 takes 150 ms per login and you expect 1,000 logins/second at peak, you need enough parallel capacity (CPU cores, or a dedicated auth service) to absorb that load without becoming a bottleneck for the whole platform. Non‑cryptographic hashing for sharding/routing must be evaluated for even key distribution, or “hot shards” emerge.
- Encryption at scale: every encrypt/decrypt operation that calls out to an external KMS adds network latency and a dependency on KMS availability. High‑throughput systems typically cache “data encryption keys” locally (encrypted themselves by a “key encryption key” from the KMS — a pattern called envelope encryption) so that bulk AES operations happen locally without a network round‑trip per record.
This “envelope encryption” pattern — used by AWS KMS, Google Cloud KMS, and most cloud providers — is exactly how large systems reconcile the security benefits of centralised key management with the performance need for local, fast bulk encryption.
High Availability & Reliability
Neither hashing nor encryption is stateless in the operational sense — both create real availability dependencies that need to be designed for, especially on the encryption side.
9.1 Reliability considerations for hashing systems
Because hashing (in the password‑storage sense) is stateless and deterministic given the same salt and input, it does not have the same availability concerns as a service with persistent state — there’s no “hash service” that must stay up in the way a database must. However, in distributed systems, non‑cryptographic hashing plays a critical role in reliability through consistent hashing, used to distribute keys across a cluster of cache or database nodes (e.g., in Memcached, DynamoDB, or Cassandra) such that adding or removing a node only reshuffles a small fraction of keys, rather than the entire dataset.
Without consistent hashing, a naive modulo‑based hash distribution (hash(key) % num_servers) would remap almost every key whenever a server was added or removed — a reliability disaster during scaling events or node failures. Consistent hashing solves this and is foundational to how large‑scale distributed caches and databases achieve both scalability and resilience.
9.2 Reliability considerations for encryption systems
Encryption introduces a reliability dependency that hashing does not have: the key must be available, or the data is permanently unreadable. This has real architectural consequences:
- KMS high availability: production KMS services are built as highly available, multi‑region systems, because a KMS outage effectively takes down every service that depends on decrypting data — this is a single point of failure that must be engineered around with retries, caching of data keys, and multi‑region key replication.
- Key backup and disaster recovery: losing an encryption key without a backup means permanently losing access to all data encrypted with it — this is often described as “cryptographic shredding” when done intentionally (a fast way to make data permanently unrecoverable by deleting only the key, without touching the much larger ciphertext).
- Failover considerations: if a system fails over to a disaster‑recovery region, that region must also have access to the same encryption keys (or key material securely replicated) — otherwise, failover succeeds at the infrastructure level but fails at the data‑access level.
A common real‑world failure: a team encrypts data with a customer‑managed key (CMK) in a KMS, and later someone accidentally deletes or disables that key during cleanup. The data is not corrupted — it’s still there — but it becomes permanently unreadable. This is why most cloud KMS providers enforce a mandatory waiting period (e.g., 7–30 days) before a key deletion is finalised, giving teams a window to notice and reverse the mistake.
Security Considerations
This chapter is a checklist of the pitfalls that cause almost every real‑world “we did crypto but got hacked anyway” story. Each one is a concrete, historically‑observed failure mode — not a hypothetical.
10.1 Security pitfalls in hashing
- Using fast hashes for passwords: SHA‑256 or MD5 alone for password storage is a critical vulnerability — always use bcrypt, scrypt, or Argon2.
- Skipping salts: without a unique salt per user, identical passwords produce identical hashes, making precomputed rainbow‑table attacks trivial, and revealing which users share passwords.
- Using broken algorithms: MD5 and SHA‑1 both have known, practical collision attacks and must never be used for security‑sensitive hashing (checksums for non‑security purposes are a different story).
- Timing attacks on comparison: comparing hash values with a naive string comparison (
==) can leak timing information about how many leading characters matched, letting attackers guess a hash byte‑by‑byte. Always use a constant‑time comparison function.
10.2 Security pitfalls in encryption
- Hard‑coding keys in source code: one of the most common real‑world breaches — keys committed to a Git repository, found by automated scanners within minutes of a public push.
- Reusing IVs / nonces: in modes like AES‑GCM, reusing the same key + IV pair for two different messages catastrophically breaks the security guarantees, potentially exposing both plaintexts.
- Using ECB mode: Electronic Codebook mode encrypts identical plaintext blocks into identical ciphertext blocks, visibly leaking patterns (famously demonstrated by the “ECB penguin” image, where the outline of a penguin is still visible after ECB encryption).
- No integrity checking: encrypting data without also authenticating it (i.e., using a non‑authenticated mode without a separate MAC) allows attackers to tamper with ciphertext undetected. AES‑GCM solves this by combining both in one operation.
- Weak key derivation from passwords: if an encryption key is derived directly from a user’s password without a proper key‑derivation function (like PBKDF2 or Argon2 used specifically for key derivation), the encryption is only as strong as the password itself.
10.3 HMAC — where hashing and secrecy meet
HMAC (Hash‑based Message Authentication Code) deserves special mention because it sits at the intersection of hashing and “keyed” security: it combines a cryptographic hash function (like SHA‑256) with a secret key to produce a value that proves both that a message hasn’t been tampered with and that it was created by someone who knows the secret key. This is exactly how webhook signatures (e.g., Stripe, GitHub) and many API authentication schemes work — the sender computes HMAC(secret_key, payload), and the receiver recomputes it independently to verify authenticity, without ever transmitting the secret key itself.
10.4 Java code example: password hashing done right
// Using Spring Security's BCryptPasswordEncoder — production-grade password hashing
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class PasswordService {
// Cost factor of 12 is a solid default in 2026 — tune based on your server's CPU budget
private final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(12);
public String hashPassword(String plainPassword) {
// BCrypt automatically generates and embeds a random salt in the output
return encoder.encode(plainPassword);
}
public boolean verifyPassword(String plainPassword, String storedHash) {
// matches() internally does a constant-time comparison — never compare hashes with ==
return encoder.matches(plainPassword, storedHash);
}
}10.5 Java code example: AES‑256‑GCM encryption
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.util.Base64;
public class EncryptionService {
private static final int GCM_TAG_LENGTH_BITS = 128;
private static final int IV_LENGTH_BYTES = 12;
public String encrypt(String plaintext, SecretKey key) throws Exception {
byte[] iv = new byte[IV_LENGTH_BYTES];
new SecureRandom().nextBytes(iv); // fresh IV for every encryption — never reuse
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH_BITS, iv);
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
byte[] ciphertext = cipher.doFinal(plaintext.getBytes("UTF-8"));
// Store IV alongside ciphertext — it's not secret, just needs to be unique per message
byte[] combined = new byte[iv.length + ciphertext.length];
System.arraycopy(iv, 0, combined, 0, iv.length);
System.arraycopy(ciphertext, 0, combined, iv.length, ciphertext.length);
return Base64.getEncoder().encodeToString(combined);
}
public String decrypt(String encoded, SecretKey key) throws Exception {
byte[] combined = Base64.getDecoder().decode(encoded);
byte[] iv = new byte[IV_LENGTH_BYTES];
byte[] ciphertext = new byte[combined.length - IV_LENGTH_BYTES];
System.arraycopy(combined, 0, iv, 0, IV_LENGTH_BYTES);
System.arraycopy(combined, IV_LENGTH_BYTES, ciphertext, 0, ciphertext.length);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH_BITS, iv);
cipher.init(Cipher.DECRYPT_MODE, key, spec);
byte[] plaintext = cipher.doFinal(ciphertext); // throws if auth tag verification fails
return new String(plaintext, "UTF-8");
}
}10.6 Java code example: HMAC for API request signing
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
public class WebhookSignatureService {
private static final String ALGORITHM = "HmacSHA256";
public String sign(String payload, String secretKey) throws Exception {
Mac mac = Mac.getInstance(ALGORITHM);
mac.init(new SecretKeySpec(secretKey.getBytes("UTF-8"), ALGORITHM));
byte[] signatureBytes = mac.doFinal(payload.getBytes("UTF-8"));
return Base64.getEncoder().encodeToString(signatureBytes);
}
public boolean verify(String payload, String secretKey, String receivedSignature) throws Exception {
String expectedSignature = sign(payload, secretKey);
// MessageDigest.isEqual performs a constant-time comparison
return java.security.MessageDigest.isEqual(
expectedSignature.getBytes("UTF-8"),
receivedSignature.getBytes("UTF-8")
);
}
}Monitoring, Logging & Metrics
Even “invisible” security primitives like hashing and encryption need to be observable in production. Here’s what mature teams typically track.
11.1 Metrics for hashing systems
Password hash latency (p50/p95/p99)
Tracks whether your cost factor is well‑tuned — too fast is insecure, too slow degrades login UX and can create thread‑pool exhaustion under load.
Failed login rate
Spikes can indicate a credential‑stuffing attack in progress, distinct from normal typo‑driven failures.
Hash algorithm version distribution
During a migration from an older algorithm (e.g., legacy MD5 to bcrypt), tracking what fraction of stored hashes are still on the old algorithm helps measure migration progress (often done via “hash upgrade on next successful login”).
11.2 Metrics for encryption systems
KMS call latency & error rate
Since decryption is often on the critical read path, KMS slowness or throttling directly impacts application latency and error budgets.
Key usage & access audit logs
Every decrypt operation should be logged (who, what key, when) for compliance (SOC 2, HIPAA, PCI‑DSS) and for detecting anomalous access patterns.
Key age / rotation compliance
Dashboards tracking how many keys are past their rotation policy deadline (e.g., “rotate every 90 days”).
Envelope encryption cache hit rate
If you cache data keys locally to avoid a KMS call per operation, monitoring the cache hit rate tells you how effectively you’re avoiding KMS load.
Never log plaintext passwords, decrypted sensitive fields, or raw encryption keys — even at DEBUG level, even temporarily. A shockingly common real‑world breach vector is sensitive data leaking into application logs, which are often far less protected than the primary database. Structured logging frameworks should have explicit field‑masking rules for known‑sensitive field names.
Deployment & Cloud Considerations
The nuts and bolts of running these primitives in AWS, GCP, Azure or on‑prem — where the managed services live, and what you should still own yourself.
12.1 Deploying hashing in cloud environments
Password hashing itself typically runs inside your application service — no special cloud infrastructure is required beyond ensuring your compute has enough CPU headroom for the chosen cost factor under peak login load. Where cloud services do get involved is around content hashing: object storage services like AWS S3 automatically compute and expose checksums (ETag, and optionally SHA‑256 via additional checksum algorithms) for uploaded objects, which applications can use to verify upload integrity without computing hashes themselves.
12.2 Deploying encryption in cloud environments
Every major cloud provider offers a managed KMS: AWS KMS, Google Cloud KMS, Azure Key Vault. These services handle the hardest parts of encryption key management — secure key generation (often backed by Hardware Security Modules, or HSMs), access control via IAM policies, automatic rotation, and detailed audit logging via services like AWS CloudTrail.
Common deployment patterns:
- Encryption at rest: most managed databases (RDS, DynamoDB, Cloud SQL) offer transparent, storage‑level encryption with a single configuration toggle, using a KMS‑managed key — this protects against physical disk theft but does not protect data from anyone with valid database query access.
- Field‑level (application) encryption: for data requiring stronger guarantees (e.g., protecting a specific column even from database administrators), applications explicitly encrypt/decrypt specific fields using the patterns shown earlier, on top of storage‑level encryption.
- Encryption in transit: TLS termination at load balancers / API gateways (AWS ALB, Google Cloud Load Balancing) using certificates from a managed certificate authority, often auto‑renewed.
- Secrets management: application‑level secrets (database passwords, API keys, HMAC signing secrets) are stored in dedicated secrets managers (AWS Secrets Manager, HashiCorp Vault) rather than environment variables or config files, with fine‑grained access policies and rotation support.
Databases, Caching & Load Balancing
Once you get past “password hashing,” both primitives quietly power a surprising amount of the data layer — from partitioning strategies to session affinity to dedup pipelines.
13.1 Hashing in databases
Hashing shows up throughout database internals well beyond password storage:
- Hash indexes: some databases support hash‑based indexes (as an alternative to B‑tree indexes) for extremely fast equality lookups, though they don’t support range queries.
- Sharding / partitioning: distributed databases (Cassandra, MongoDB, DynamoDB) use a hash of the partition/shard key to decide which physical node or shard owns a given row, aiming for even data distribution across the cluster.
- Deduplication: content‑addressable storage systems compute a hash of file/blob content and use that hash as the storage key — if two uploads produce the same hash, the system knows they’re identical and can avoid storing duplicate data (used by Git internally, and by many backup / dedup systems).
13.2 Hashing in caching and load balancing
Consistent hashing (introduced in section 9) is the backbone of how large‑scale caches (Memcached, Redis Cluster) and CDNs route requests: hashing a cache key (like a URL) deterministically maps it to the same cache node every time, without needing a centralised lookup table — critical for both performance and horizontal scalability. Load balancers similarly offer “hash‑based” routing algorithms (e.g., hashing on source IP or a session cookie) to achieve session affinity (“sticky sessions”) without maintaining server‑side session state.
13.3 Encryption in databases
Beyond Transparent Data Encryption (TDE) for full‑database encryption at rest, some databases support native column‑level encryption (e.g., MySQL’s AES_ENCRYPT()/AES_DECRYPT() functions, PostgreSQL’s pgcrypto extension). Application‑level encryption (encrypting in your service code before the write, as shown in the Java example earlier) is generally preferred for sensitive fields because it keeps the plaintext out of the database entirely — even a full database dump or a malicious DBA cannot see the real values without also having access to the KMS.
13.4 A worked example: designing a “forgot password” and secure‑storage flow
| Data | Protection method | Reasoning |
|---|---|---|
| User’s password | Argon2 hash + salt | Never needs to be recovered, only verified |
| Password reset token | Random token, stored as SHA‑256 hash; raw token emailed to user | Same principle as passwords — the DB never needs the raw token, only to verify a match |
| User’s stored payment card (tokenised reference) | Encrypted via PCI‑compliant vault / tokenisation service | Must be recoverable for refunds / re‑charges, under strict compliance controls |
| Session cookie / JWT | HMAC‑signed (integrity) — often not encrypted, since claims aren’t secret | Server needs to verify the token wasn’t tampered with, not keep its contents secret from the client itself |
APIs & Microservices
Public APIs and service meshes lean heavily on both primitives — hashing to prove “this request wasn’t tampered with,” encryption to keep it private on the wire.
14.1 Hashing in API design
APIs use hashing extensively for purposes beyond passwords:
- ETags for HTTP caching: a server computes a hash of a resource’s content and returns it as an
ETagheader; clients send it back viaIf-None-Matchon subsequent requests, letting the server respond with a cheap304 Not Modifiedinstead of resending the full payload. - Idempotency keys: many payment and order‑creation APIs (Stripe being the canonical example) let clients supply an idempotency key so retried requests (due to network failures) don’t create duplicate side effects — the server often hashes the key alongside the request body to detect conflicting retries.
- API request signing: as shown in the HMAC example, many APIs (AWS Signature V4, webhook providers) require requests to be signed with an HMAC derived from a shared secret, letting the server verify both authenticity and integrity without a full TLS mutual‑auth setup.
14.2 Encryption in API design and microservices
- Mutual TLS (mTLS): in microservice meshes (Istio, Linkerd), services authenticate each other and encrypt traffic between them using mutual TLS, so that even internal, “trusted network” traffic is encrypted and each side cryptographically verifies the other’s identity.
- JWT encryption vs signing: most JWTs are only signed (integrity, via HMAC or RSA signatures) — their contents are readable by anyone (just Base64‑decode them), which surprises many engineers. If a JWT needs to keep its claims confidential, you need a JWE (JSON Web Encryption) token, a distinct, less commonly used standard.
- Encrypting secrets in transit between services: service‑to‑service calls carrying sensitive payloads (PII, financial data) typically rely on TLS in transit plus, in stricter environments, payload‑level encryption for defense‑in‑depth against a compromised intermediate service or misconfigured proxy.
“JWTs are encrypted, so it’s safe to put sensitive data in the payload.” This is false for standard signed JWTs (JWS) — they’re only tamper‑evident, not confidential. Anyone can decode the payload without any key at all. Never put secrets or sensitive PII directly in a standard JWT’s claims.
Design Patterns & Anti‑Patterns
A short catalogue of the habits that keep cryptographic systems safe for years, and the ones that reliably reintroduce risk after a well‑meaning refactor.
15.1 Good patterns
Envelope encryption
Encrypt bulk data with a local data key, encrypt that key with a KMS‑managed master key — balances performance and centralised key control (covered in section 8.3).
Salted, slow password hashing
bcrypt/Argon2 + per‑user random salt + appropriate cost factor, always.
Tokenisation for regulated data
Instead of encrypting sensitive data like credit card numbers directly in your systems, replace it with a non‑sensitive “token” and store the real value in a dedicated, tightly controlled vault — reduces the compliance scope (e.g., PCI‑DSS) of the rest of your system entirely.
Defense in depth
Layer encryption at multiple levels (transit + at‑rest + field‑level) so a failure in one layer doesn’t fully expose data.
Hash‑then‑sign for large payloads
Rather than digitally signing an entire large document with slow asymmetric cryptography, hash the document first (fast) and sign only the small, fixed‑size hash — this is exactly how digital signatures work in practice.
15.2 Anti‑patterns to avoid
Rolling your own crypto
Writing a custom hashing or encryption algorithm “for fun” or “for speed” is one of the most reliable ways to introduce a critical vulnerability. Always use vetted, standard libraries and algorithms.
“Encrypting” passwords instead of hashing
If your system can ever display a user’s original password back to them (e.g., in a “forgot password” email), it is fundamentally insecure and must be redesigned.
Keys next to the data they protect
e.g., a key stored in the same database as the ciphertext, or in the same config file deployed alongside the application — defeats the purpose of encryption entirely if that store is ever breached.
Same salt for every user
Provides almost no additional protection over no salt at all, since an attacker can precompute one rainbow table for the whole system.
Deprecated algorithms out of habit
MD5 and SHA‑1 for anything security‑related, DES or single‑key RC4 for encryption — all considered broken or too weak by modern standards.
“Just hash it” on low‑entropy data
Hashing predictable, low‑entropy data (like a 10‑digit phone number or a 9‑digit SSN) with a plain unsalted hash is nearly as bad as storing it in plaintext — the entire space of possible inputs can be hashed and compared in seconds. Use HMAC with a secret key instead, or encrypt it properly.
Best Practices & Common Mistakes
Most cryptographic incidents don’t come from missing knowledge — they come from a checklist item that quietly stopped being followed. Treat the lists below as things to actively verify.
16.1 Best practices checklist
Hashing checklist
- Use Argon2id (preferred) or bcrypt for password hashing
- Generate a unique random salt per record
- Tune cost factor based on real load testing, not guesswork
- Use HMAC (not plain hash) when a shared secret is involved
- Use SHA‑256/SHA‑3 for non‑password integrity checks
- Compare hashes using constant‑time functions
- Plan a migration path for upgrading legacy hash algorithms
Encryption checklist
- Use AES‑256‑GCM (or ChaCha20‑Poly1305) for symmetric encryption
- Never reuse an IV/nonce with the same key
- Manage keys via a dedicated KMS, never hardcode them
- Rotate keys on a defined schedule
- Use envelope encryption for high‑throughput bulk data
- Log and audit every key access / decrypt operation
- Encrypt sensitive data at the application layer, not just storage layer
16.2 Common mistakes engineers make (and how to avoid them)
| Mistake | Consequence | Fix |
|---|---|---|
| Using MD5 for password hashing | Passwords crackable in seconds/minutes with modern GPUs | Migrate to Argon2/bcrypt with a background re‑hash‑on‑login strategy |
| Storing encryption key in application config file in source control | Key exposure via repository history, even after later removal | Use a secrets manager; rotate the exposed key immediately |
Comparing hashes with String.equals() | Timing side‑channel potentially leaks hash contents | Use MessageDigest.isEqual() or a library’s constant‑time comparator |
| Believing “hashed” means “safe to log / expose” | Weak/low‑entropy hashed data can still be reversed via brute‑force or lookup tables | Treat unsalted hashes of predictable data as sensitive; use HMAC or encryption instead |
| Encrypting data but never rotating keys | A single old key compromise exposes years of historical data | Enforce key rotation policies with automated tooling |
Real‑World / Industry Examples
A whistle‑stop tour of the systems and companies whose crypto decisions have shaped how the industry thinks today.
17.1 Hashing in the wild
- GitHub / Git: every commit, file, and tree in Git is identified by a SHA‑1 (moving toward SHA‑256) hash of its content — this is what makes Git a “content‑addressable” system and lets it detect any tampering in a repository’s history instantly.
- Netflix: uses consistent hashing extensively across its distributed caching and data‑partitioning infrastructure to route requests evenly across thousands of servers while minimising reshuffling during scaling events.
- Password managers & “Have I Been Pwned”: Troy Hunt’s breach‑checking service lets users check if their password appears in a known breach without ever sending the actual password — it uses a clever technique called k‑anonymity, where only the first 5 characters of a SHA‑1 hash of the password are sent to the API, and the client checks the full hash locally against a returned list of candidates.
- Blockchain (Bitcoin, Ethereum): every block is cryptographically linked to the previous one via its hash, and “mining” is fundamentally a brute‑force search for an input that produces a hash meeting a specific difficulty target (proof‑of‑work).
17.2 Encryption in the wild
- Signal / WhatsApp: use the Signal Protocol, combining asymmetric key exchange (via the Double Ratchet algorithm) with symmetric AES encryption, to provide true end‑to‑end encryption where not even the service provider can read message content.
- Amazon S3: offers multiple encryption‑at‑rest options (SSE‑S3, SSE‑KMS, SSE‑C) so customers can choose their preferred level of key control, with SSE‑KMS being the most common for regulated workloads due to its audit‑logging integration.
- Apple’s data protection: iOS devices use hardware‑backed encryption keys tied to the Secure Enclave, meaning even Apple cannot decrypt certain categories of on‑device data without the user’s passcode.
- Banking & payment networks: PCI‑DSS compliance requires encryption of cardholder data both at rest and in transit, typically implemented via a combination of TLS, field‑level encryption, and tokenisation vaults from providers like Stripe or Braintree.
17.3 A combined real‑world example: how login actually works end‑to‑end
Notice how this single, everyday flow uses TLS encryption (protecting data in transit), Argon2 hashing (protecting the stored password), and HMAC signing (protecting the issued JWT’s integrity) — three distinct cryptographic tools, each solving a different problem, working together in one request.
FAQ, Summary & Key Takeaways
The last stop — the questions that come up almost every time an engineer, product manager, or founder first thinks about how their login system actually works, plus the ideas worth carrying away.
Frequently Asked Questions
Can a hash be reversed if you have enough computing power?
Not in the way encryption can be decrypted. There is no mathematical inverse function for a good cryptographic hash. What attackers actually do is guess possible inputs, hash each guess, and check for a match (brute‑force or dictionary attacks) — they never “decrypt” the hash itself. This is why slow, salted hashing algorithms for passwords are effective: they make each guess expensive rather than trying to prevent guessing altogether.
Is it possible for two different inputs to produce the same hash (a collision)?
Yes, theoretically — since hash outputs are fixed‑length but inputs can be infinitely long, collisions must exist by the pigeonhole principle. For a well‑designed algorithm like SHA‑256, finding one is computationally infeasible with current technology (expected to require roughly 2^128 attempts). For broken algorithms like MD5 and SHA‑1, practical collision attacks have been demonstrated, which is exactly why they’re deprecated for security use.
Why can’t I just encrypt passwords instead of hashing them?
Because encryption is reversible by design — anyone with the decryption key (including an attacker who compromises your key management, or a malicious insider) can recover every user’s actual password. Hashing removes that risk entirely, since even you, the system owner, cannot recover the original password from its hash.
What’s the difference between hashing and encoding (like Base64)?
Encoding is fully reversible with no secret required at all — it exists purely to represent data in a different format (e.g., binary data as printable text), not to provide any security. Hashing is one‑way and, for cryptographic hashes, resistant to reversal even with unlimited effort in a practical sense. Never mistake Base64 for “encryption” or “security” — it provides none.
Should I use symmetric or asymmetric encryption for my application?
Use symmetric encryption (AES) for bulk data you control both ends of — it’s fast and simple. Use asymmetric encryption (RSA/ECC) specifically for scenarios involving two parties who haven’t shared a secret in advance, such as key exchange, digital signatures, or public‑facing certificate‑based authentication. In practice, most real systems (like TLS) use both together in a hybrid approach.
What is “salting,” and why does it matter so much?
A salt is random data added to an input before hashing, unique per record. It ensures that identical inputs (like two users with the same password) produce different hash outputs, and it defeats precomputed attack tables (rainbow tables) because an attacker would need a separate table for every possible salt value — which is computationally impractical if salts are sufficiently random and unique.
Is hashing used for anything other than security?
Absolutely — non‑cryptographic hashing is foundational to hash tables/maps (like Java’s HashMap), database sharding, load balancer routing, caching layers, and deduplication systems. These use cases prioritise speed and even distribution over cryptographic strength, so they often use much simpler, faster algorithms than SHA‑256.
Summary
Hashing and encryption are both cryptographic tools, but they exist to answer fundamentally different questions. Hashing takes any input and deterministically produces a fixed‑size, one‑way fingerprint — perfect for verifying that something matches a known value (passwords, file integrity, message authenticity) without ever needing to recover the original. Encryption takes readable data and transforms it into unreadable ciphertext using a key, with the explicit expectation that an authorised party can reverse the process and recover the original — perfect for protecting confidentiality of data that legitimately needs to be read again later.
In production systems, these tools are rarely used in isolation. A single login request might involve TLS encryption in transit, Argon2 hashing for password verification, and HMAC signing for the resulting session token — three different cryptographic primitives, each doing a distinct job, working together. Understanding which one to reach for — and why — is one of the clearest signals of security maturity in a software engineer, and getting it wrong is one of the most common root causes of real‑world data breaches.
Key takeaways
- Hashing is one‑way and irreversible by design; encryption is two‑way and reversible by design with the correct key.
- Never use encryption for passwords, and never expect to recover data from a hash — pick the tool that matches whether you need to verify or recover.
- Always use purpose‑built, slow, salted algorithms (bcrypt, Argon2) for password hashing — never a fast general‑purpose hash alone.
- Always use authenticated encryption modes (AES‑256‑GCM) and manage keys through a dedicated KMS with rotation and auditing — never hardcode keys.
- HMAC bridges the two worlds — a keyed hash used for authenticity and integrity without needing full encryption.
- Non‑cryptographic hashing (consistent hashing, hash tables) is a completely separate, performance‑focused use case from cryptographic hashing, and the two should never be confused.
- Real production systems combine symmetric encryption, asymmetric encryption, and hashing together — understanding how they interlock (as in TLS or a typical login flow) is essential architectural knowledge.