A new developer learns three vocabulary words in their first week: encoding, hashing, and encryption. By the end of the second week they have used the wrong one in production and shipped a security bug. The three terms are not interchangeable: encoding is a reversible representation, hashing is a one-way fingerprint, encryption is a reversible transformation protected by a key. Picking the wrong one is one of the most common classes of security failure in modern web applications.
The Three Operations at a Glance
All three take an input and produce an output. What differs is whether the output can be reversed, and whether the reversal requires a key.
- Encoding is a reversible mapping from one representation to another. The output can be decoded back to the original input without any secret.
Base64,URL encoding, andhexare encoding schemes. The purpose is to fit data into a transport that cannot carry it directly (raw bytes cannot travel in a URL; binary data cannot be embedded in a JSON string). - Hashing is a one-way function. The same input always produces the same output, but the output cannot be reversed to the input.
MD5,SHA-256, andSHA-3are hash functions. The purpose is to verify integrity (has the input changed?) and to fingerprint data (a short identifier for a long input). - Encryption is a reversible transformation that requires a key. The output can be decrypted back to the original only by someone holding the key.
AES,RSA, andChaCha20are encryption primitives. The purpose is confidentiality: keeping the input secret from anyone who does not have the key.
The cardinal rule: encoding is for transport, hashing is for verification, encryption is for confidentiality. If you forget which is which, your security model is broken.
Encoding: Reversible and Public
Base64 is the canonical example. It encodes binary data as a 64-character ASCII alphabet so it can travel through text-only channels (email bodies, JSON strings, URLs). The encoded string is longer than the input (about 33% overhead) but is reversibly decodable by anyone with no key, no secret, no permission. Encoding is not a security primitive. If you need to hide a value from prying eyes, Base64 is the wrong tool.
URL encoding (percent-encoding) is another example. It encodes arbitrary text into a URL-safe form by replacing non-ASCII characters with %XX sequences. The encoded string is reversible; the receiving server decodes it back to the original text. Like Base64, URL encoding is a transport detail, not a security measure.
The test for whether something is encoding: can it be reversed without a key? If yes, it is encoding (or decryption with a known key). Encoding is generally safe to use anywhere without security implications; it is the cheapest of the three operations and exists to make data formats interoperable.
Hashing: One-Way Fingerprints
Hashing produces a fixed-size output from any input. The same input always produces the same hash. A one-character change in the input produces a completely different hash. The output is unpredictable: given a hash, you cannot find the input that produced it. crypto.subtle.digest('SHA-256', data) computes a 256-bit hash; the Hash Generator exposes MD5, SHA-1, SHA-256, SHA-384, and SHA-512.
Hashing is used for integrity (a downloaded ISO whose SHA-256 matches the publisher’s checksum is unchanged), for deduplication (two files with the same hash are assumed identical), and for password storage (the server stores hash(password + salt) rather than the password itself). Hashing is also used inside digital signatures: the signature is over the hash, not the full message, because asymmetric operations on large inputs are slow.
The test for whether something is hashing: is it reversible? A correct hash function is not reversible in any practical sense. No algorithm can recover the input from the hash, even with unlimited compute. If a system claims to “decrypt” a hash, it is either lying or it has a precomputed table (a rainbow table) of common inputs.
Hashing has pitfalls. Plain hashes of passwords are vulnerable to rainbow tables because the same password always produces the same hash. The fix is to add a random salt before hashing: hash(salt + password). For higher security, use a memory-hard key derivation function like Argon2id, bcrypt, or scrypt, which deliberately slow down the hashing to defeat brute-force attacks against the stored hashes.
Encryption: Reversible with a Key
Encryption produces output that can be reversed back to the input if and only if you have the key. Without the key, the ciphertext is indistinguishable from random noise. There are two main categories:
- Symmetric encryption uses the same key for encryption and decryption.
AES-256-GCMandChaCha20-Poly1305are the modern symmetric primitives. The challenge is key distribution: how do both parties get the same key without an attacker learning it? - Asymmetric encryption uses a key pair: a public key for encryption and a private key for decryption.
RSAandElliptic Curve Cryptography (ECC)are the main families. The public key can be shared freely; the private key is kept secret. This solves the key distribution problem but is much slower than symmetric encryption.
The hybrid approach used everywhere in practice: use asymmetric encryption to securely exchange a random symmetric key, then use that symmetric key for the bulk of the data. This is what TLS does during the handshake. NIST-approved primitives include AES-256, ChaCha20, RSA-2048 (or larger), and X25519 for key exchange.
Encryption is the only one of the three operations that provides confidentiality. If you need to keep data secret from anyone who does not have the key, encryption is the only tool that does the job. Encoding and hashing both leak the input: encoding is trivially reversible, and hashing is vulnerable to brute-force and rainbow-table attacks when the input has low entropy.
Classic Mistakes
These three errors continue to recur in production codebases, security audits, and CTF write-ups.
- Using Base64 for security. “We encrypted the JWT with Base64.” This is not encryption. Base64 is encoding; the output is reversed by anyone with a computer. The original input is “merely” obscured, not protected.
- Hashing without salting. The server stores
hash(password)and a breach exposes the hashes. The attacker builds a rainbow table of common passwords and reverses most of them in seconds. The fix is per-user salts and a memory-hard KDF. - Encoding instead of encrypting sensitive data. Storing credit card numbers in a database by URL-encoding them. The encoding is reversible, so the database breach exposes everything. The right answer is field-level encryption with a key management system.
- Encrypting when hashing is correct. Storing passwords encrypted so the application can decrypt them and verify. The application becomes a single point of failure: a breach exposes every user’s password in plaintext. The right answer is to hash the password on the server and compare hashes on login.
Decision Tree
When you are about to apply one of the three operations, ask these questions in order:
- Do I need to recover the original data later? If yes, you need encoding (no key needed) or encryption (key needed). If no, you need hashing.
- Do I need to keep the data secret from unauthorized parties? If yes, you need encryption. If no, encoding or hashing is sufficient.
- Am I verifying integrity (has the data changed?) rather than protecting it? If yes, hashing is correct. If no, you probably need encryption.
- Am I trying to hide information from a casual observer? If yes, encoding is enough. If no, encryption is required.
Branching through the tree settles most implementation questions. The remaining ones are usually about which specific algorithm to use (AES-256-GCM vs. ChaCha20-Poly1305) and which key management approach fits the deployment.
What TLS Actually Does
TLS, the protocol that secures HTTPS, exercises all three operations in a single handshake. When a browser connects to a server:
- The two parties exchange hello messages and agree on a cipher suite.
- The server sends a certificate containing its public key, signed by a trusted CA. Verifying the signature is a hash-based operation (the CA hashes the certificate and signs the hash).
- The client generates a random pre-master secret, encrypts it with the server’s public key (asymmetric encryption), and sends it to the server.
- Both parties derive symmetric session keys from the pre-master secret. From this point on, all traffic is encrypted with the symmetric key.
- Each TLS record includes a MAC (Message Authentication Code), which is a hash-based operation verifying that the record has not been tampered with in transit.
Every TLS handshake touches all three primitives. The hyphens are well-worn: encoding for the protocol framing, hashing for integrity, and encryption for confidentiality. Knowing the boundary between them is the foundation of actually understanding TLS rather than just using it.
How to Verify the Right Tool Was Used
When you encounter a system, you can check whether the right primitive was used by looking at the operation names and the surrounding API:
atob,btoa,encodeURIComponent,decodeURIComponent— encoding. No key involved. Not security.crypto.subtle.digest,SHA-256libraries — hashing. One-way. No key involved.crypto.subtle.encrypt,crypto.subtle.decrypt,crypto.subtle.sign,crypto.subtle.verify— encryption. Requires a key. Asymmetric usespublicKey/privateKeyfields; symmetric uses a single key.
A red flag: a code path that claims to “decrypt” a value that was created with a hash function, or that “encrypts” a value with a hash function. These are misuse of the API. A common variant is a system that hashes a password and then “decrypts” the hash on login to compare against the user input. The correct flow is to hash the user input and compare the two hashes.
The Bottom Line
Encoding, hashing, and encryption are three different tools with three different purposes. Encoding is reversible for transport; hashing is irreversible for verification; encryption is reversible with a key for confidentiality. If you find yourself using one where another belongs, you have a bug. The good news is that the right tool is usually obvious once you have the question framed: do I need to recover this? does it need to be secret? am I just checking integrity? The answers map directly to the three primitives.
Further Reading
- RFC 4648 — The Base16, Base32, and Base64 Data Encodings, the formal specification of the most common encoding schemes.
- NIST FIPS 180-4 — the Secure Hash Standard, the formal definition of SHA-1, SHA-256, SHA-384, and SHA-512.
- NIST FIPS 197 — the Advanced Encryption Standard (AES), the formal specification of the most widely used symmetric cipher.
- RFC 8017 — PKCS #1: RSA Cryptography Specifications, the formal specification of RSA.
- Cryptography Engineering by Ferguson, Schneier, and Kohno — the practical book on choosing and applying primitives.
Frequently Asked Questions
Is hashing more secure than encryption? They do different things. Hashing protects integrity (you cannot be fooled into accepting a modified input); encryption protects confidentiality (an attacker without the key cannot read the input). For passwords, hashing is correct because the server never needs to recover the original password. For messages you want to send securely, encryption is correct because the recipient must recover the plaintext.
Can I encrypt a hash to get extra security? Encrypting a hash is a redundant operation in most cases. The hash has no recoverable plaintext, so encrypting it adds no security against confidentiality attacks. The typical use case is to combine a hash (for integrity) with a signature (for authenticity), which is what digital signatures do.
Is bcrypt a hash or encryption? Bcrypt is a password hashing function, not encryption. It is deliberately slow and includes a salt to defeat brute-force attacks. The output is irreversible; the same input always produces the same output given the same salt and cost.
Why do we still use Base64 if it is not security? Base64 is the universal transport format for binary data in text contexts. JWTs use it, data URIs use it, email attachments use it. The fact that it is not security is not a flaw; it is a feature of being a transport format. The flaw is in systems that treat Base64 as a security boundary.