Skip to content
ZeroServer.tools
All guides

Encryption vs Hashing: What's the Difference and When to Use Each

June 10, 2026 · 6 min read

Encryption vs Hashing: What's the Difference and When to Use Each

Encryption and hashing are both cryptographic operations that transform data, but they serve fundamentally different purposes. Using the wrong one is not just a technical mistake — it's a security vulnerability. Encrypting passwords instead of hashing them has led to some of the largest data breaches in history.

This guide explains both concepts clearly and gives you the decision framework to always choose correctly.

What Is Hashing?

Hashing is a one-way function: it transforms input data of any size into a fixed-length output called a hash or digest. There is no key, and there is no way to reverse the operation and recover the original input.

SHA-256("hello") → 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
SHA-256("Hello") → 185f8db32921bd46d35767cdf892e8f0a42d69f7e1df3c8c5e3c8e832d0b7e7a

Key properties:

  • Deterministic — the same input always produces the same output
  • Fixed output length — SHA-256 always outputs 256 bits, regardless of input size
  • Avalanche effect — a tiny change in input produces a completely different hash (note how "hello" and "Hello" differ entirely above)
  • Irreversible — you cannot reconstruct the input from the hash

Use hashing for:

  • Storing passwords
  • Verifying file integrity (checksums)
  • Generating content identifiers (git commit hashes are SHA-1)
  • Building lookup tables and hash maps

What Is Encryption?

Encryption is a two-way function: data is transformed into an unreadable ciphertext using a key, and can be transformed back into the original plaintext using a key. The critical word is reversible.

There are two major categories:

Symmetric encryption — the same key encrypts and decrypts. Fast, efficient, suitable for large data. The standard is AES-256 (Advanced Encryption Standard with a 256-bit key):

AES-256-GCM encrypt("credit card number", key) → [ciphertext]
AES-256-GCM decrypt([ciphertext], key) → "credit card number"

Asymmetric encryption — uses a key pair: a public key to encrypt, a private key to decrypt (or vice versa for signing). Slower than symmetric, but solves the key distribution problem. Standards include RSA and ECDSA:

RSA encrypt(data, public_key)  → [ciphertext]
RSA decrypt([ciphertext], private_key) → data

Use encryption for:

  • Data in transit (TLS/HTTPS uses both asymmetric and symmetric)
  • Sensitive data at rest that needs to be retrieved (credit cards, API keys, PII)
  • Signing data (JWT tokens, code signing certificates)

The Decision Framework

Do you need to recover the original value later?
├── YES → Encryption
│   ├── Protecting data in transit → TLS (handles this for you)
│   ├── Storing retrievable secrets → AES-256-GCM (symmetric)
│   └── Signing / key exchange → RSA or ECDSA (asymmetric)
└── NO → Hashing
    ├── Storing passwords → bcrypt or Argon2
    └── Verifying file integrity → SHA-256

Passwords belong in the "NO" column. You never need to recover a user's password — you only need to verify that what they typed matches what you stored. Hash it.

Credit card numbers belong in the "YES" column. Your payment system needs to retrieve and use the actual number. Encrypt it.

Why MD5 and SHA-1 Are Broken for Passwords

MD5 and SHA-1 are cryptographic hash functions, so they produce irreversible hashes — that's correct. The problem is speed. They were designed for performance: checksumming files, generating content IDs, signing certificates. Modern hardware can compute billions of MD5 hashes per second.

An attacker with a database of stolen password hashes can run every word in a dictionary, every combination of 8 characters, through MD5 at billions of guesses per second. This is called a brute-force attack, and it cracks most simple passwords within minutes.

Bcrypt, scrypt, and Argon2 were specifically designed to be slow. They include a configurable cost factor that you tune so each hash takes ~100–300ms on your hardware. When an attacker tries to brute-force bcrypt hashes, they can only test thousands of guesses per second instead of billions. The same attack that takes minutes against MD5 takes centuries against bcrypt.

# WRONG — never do this
import hashlib
hashed = hashlib.md5(password.encode()).hexdigest()

# RIGHT — use bcrypt
import bcrypt
hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))

Rule: Never use a general-purpose hash function (MD5, SHA-256, SHA-3) alone for password storage. Always use a purpose-built password hashing function: bcrypt, scrypt, or Argon2id.

Rainbow Table Attacks and Salting

A rainbow table is a precomputed database of hash → input mappings for common passwords. Without salting, an attacker with a rainbow table can look up 5f4dcc3b5aa765d61d8327deb882cf99 and instantly know it's the MD5 of "password".

A salt is a random value generated per-password and stored alongside the hash. The password is hashed as hash(password + salt). Because every hash has a unique salt, rainbow tables are useless — the attacker would need a separate rainbow table for each salt value.

User A: password = "secret123", salt = "a8f3k2"
Hash = SHA-256("secret123a8f3k2") → unique hash A

User B: password = "secret123", salt = "9x2m7p"
Hash = SHA-256("secret123x2m7p") → unique hash B (completely different)

The good news: bcrypt and Argon2 handle salting automatically. You don't manage salts manually — the library generates, stores, and uses them transparently.

HMAC: Authenticated Hashing

HMAC (Hash-based Message Authentication Code) combines a hash function with a secret key to produce a message authentication code. It's not encryption — you still can't recover the input — but it proves both that the message hasn't changed AND that it was produced by someone with the key.

import hmac, hashlib

key = b"secret_key"
message = b"user_id=42&role=admin"
mac = hmac.new(key, message, hashlib.sha256).hexdigest()
# → "a5f8c2d..."

# Verification: recompute and compare
is_valid = hmac.compare_digest(mac, expected_mac)

HMAC is used in: JWT tokens (HS256), API request signing (AWS Signature V4), cookie integrity verification.

Common Security Mistakes

Encrypting passwords instead of hashing them — If your user database is breached, attackers can steal your encryption key and decrypt every password. With hashing, there's no key to steal.

Using MD5 or SHA-1 for passwords — See above. These are not appropriate for password storage under any circumstances.

Rolling your own crypto — Writing custom encryption or hashing algorithms is a path to disaster. Use audited libraries: bcrypt, libsodium, OpenSSL, the cryptography package in Python, crypto in Node.js.

Storing passwords in plaintext — This still happens. It means every database breach exposes every password immediately.

Using the same salt for all passwords — A single shared salt eliminates rainbow table resistance but doesn't protect against targeted brute-force. Always use per-password random salts (which bcrypt/Argon2 handle automatically).

Tools

Generate SHA-256, MD5, and other hashes from any input with the Hash Generator. For testing AES encryption and decryption interactively, the AES Encryption Tool provides a hands-on way to understand symmetric encryption. Before hashing real passwords, evaluate their baseline strength with the Password Strength Checker.

The decision tree is simple: need to get the data back → encrypt. Verifying data matches without retrieving it → hash. Using that mental model consistently eliminates an entire class of security vulnerability.