Hashing vs Encryption — Quick Comparison

Category: Security

The Simple Explanation

Imagine you have a secret message. There are two ways to protect it:

  Hashing Encryption
Analogy A meat grinder — once you grind meat, you can’t un-grind it A lockbox — you lock it, and anyone with the key can unlock it
Reversible? ❌ No — one-way only ✅ Yes — with the right key
Use for Verifying things (passwords, file integrity) Protecting secrets you need to read later

Visual Overview

flowchart TB subgraph Hashing["🔒 HASHING - One-Way"] direction LR H1["📄 Original Data"] --> H2["⚙️ Hash Function"] H2 --> H3["🔢 Fixed Hash"] end subgraph Encryption["🔐 ENCRYPTION - Two-Way"] direction LR E1["📄 Original Data"] --> E2["🔑 Encrypt with Key"] E2 --> E3["🔒 Encrypted Data"] E3 --> E4["🔑 Decrypt with Key"] E4 --> E5["📄 Original Data"] end Hashing ~~~ Encryption

Hashing: The One-Way Street

What is it?

Hashing takes any input (a password, a file, a message) and turns it into a fixed-length string of characters called a “hash” or “digest”.

The key thing: You can’t reverse it. You can’t get the original back from the hash.

flowchart LR A["Any Input
(password, file, text)"] --> B["Hash Function
(SHA-256)"] B --> C["Fixed-Length Output
64 characters"] style A fill:#dbeafe,stroke:#2563eb style B fill:#fef3c7,stroke:#d97706 style C fill:#d1fae5,stroke:#059669

Real-World Analogy

Think of a fingerprint:

  • Every person has a unique fingerprint
  • You can identify someone by their fingerprint
  • But you can’t recreate the person from their fingerprint!

Hashing works the same way — it creates a unique “fingerprint” of your data.

Python Example 1: Basic Hashing

import hashlib

# Let's hash a simple message
message = "Hello, World!"

# Create a SHA-256 hash
hash_result = hashlib.sha256(message.encode()).hexdigest()

print(f"Original message: {message}")
print(f"Hash: {hash_result}")

Output:

Original message: Hello, World!
Hash: dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f

Notice how the hash is always 64 characters long, no matter how long or short your message is!

Python Example 2: Same Input = Same Hash

import hashlib

# Hash the same message twice
message = "secret123"

hash1 = hashlib.sha256(message.encode()).hexdigest()
hash2 = hashlib.sha256(message.encode()).hexdigest()

print(f"First hash:  {hash1}")
print(f"Second hash: {hash2}")
print(f"Are they the same? {hash1 == hash2}")

Output:

First hash:  5f78c33274e43fa9de5659265c1d917e25c03722dcb0b8d27db8d5feaa813953
Second hash: 5f78c33274e43fa9de5659265c1d917e25c03722dcb0b8d27db8d5feaa813953
Are they the same? True

This is how password verification works! You don’t store the password, you store its hash.

Python Example 3: Tiny Change = Completely Different Hash

import hashlib

# Two messages that differ by just one character
message1 = "Hello"
message2 = "hello"  # lowercase 'h'

hash1 = hashlib.sha256(message1.encode()).hexdigest()
hash2 = hashlib.sha256(message2.encode()).hexdigest()

print(f"'Hello' → {hash1[:20]}...")
print(f"'hello' → {hash2[:20]}...")
print(f"Are they the same? {hash1 == hash2}")

Output:

'Hello' → 185f8db32271fe25f5...
'hello' → 2cf24dba5fb0a30e26...
Are they the same? False

Even a tiny change produces a completely different hash. This makes it impossible to “guess” the original.

Python Example 4: Password Verification (How Websites Do It)

import hashlib

# ===== When user creates account =====
password = "MySecurePassword123"

# Store this hash in the database (NOT the password!)
stored_hash = hashlib.sha256(password.encode()).hexdigest()
print(f"Stored in database: {stored_hash}")

# ===== When user logs in =====
login_attempt = "MySecurePassword123"

# Hash what they entered and compare
attempt_hash = hashlib.sha256(login_attempt.encode()).hexdigest()

if attempt_hash == stored_hash:
    print("✅ Login successful!")
else:
    print("❌ Wrong password!")

# ===== Wrong password attempt =====
wrong_attempt = "wrongpassword"
wrong_hash = hashlib.sha256(wrong_attempt.encode()).hexdigest()

if wrong_hash == stored_hash:
    print("✅ Login successful!")
else:
    print("❌ Wrong password!")

Output:

Stored in database: a4e16a...
✅ Login successful!
❌ Wrong password!

How Password Verification Works

sequenceDiagram participant U as 👤 User participant W as 🌐 Website participant D as 🗄️ Database Note over U,D: Registration U->>W: Creates password: "secret123" W->>W: Hash it → "a1b2c3..." W->>D: Store hash (NOT password) Note over U,D: Login Attempt U->>W: Enters password: "secret123" W->>W: Hash it → "a1b2c3..." W->>D: Get stored hash D-->>W: Returns "a1b2c3..." W->>W: Compare hashes W-->>U: ✅ Match! Login successful

When to Use Hashing

  • ✅ Storing passwords (never store actual passwords!)
  • ✅ Checking if a file was modified (file integrity)
  • ✅ Verifying downloads aren’t corrupted
  • ✅ Creating unique IDs from content

Encryption: The Lockbox

What is it?

Encryption scrambles your data so that only someone with the right “key” can unscramble it.

The key thing: It’s reversible! If you have the key, you can get the original back.

flowchart LR subgraph encrypt["Encrypt"] A["📄 Secret Message"] --> B["🔑 + Key"] B --> C["🔒 Encrypted
(Unreadable)"] end subgraph decrypt["Decrypt"] C --> D["🔑 + Key"] D --> E["📄 Secret Message"] end style A fill:#dbeafe,stroke:#2563eb style C fill:#fecaca,stroke:#dc2626 style E fill:#d1fae5,stroke:#059669

Real-World Analogy

Think of a lockbox:

  • You put your valuables inside and lock it with a key
  • The valuables are safe from anyone who doesn’t have the key
  • With the key, you can open it and get your valuables back

Python Example 1: Basic Encryption

First, install the cryptography library:

pip install cryptography

Then:

from cryptography.fernet import Fernet

# Step 1: Generate a secret key (keep this safe!)
key = Fernet.generate_key()
print(f"Your secret key: {key.decode()}")

# Step 2: Create a cipher using the key
cipher = Fernet(key)

# Step 3: Encrypt a message
original_message = "This is my secret message!"
encrypted_message = cipher.encrypt(original_message.encode())

print(f"\nOriginal: {original_message}")
print(f"Encrypted: {encrypted_message.decode()}")

# Step 4: Decrypt it back
decrypted_message = cipher.decrypt(encrypted_message).decode()
print(f"Decrypted: {decrypted_message}")

Output:

Your secret key: ZmDfcTF7_60GrrY3vj...

Original: This is my secret message!
Encrypted: gAAAAABl...long random string...
Decrypted: This is my secret message!

Python Example 2: Encrypting Sensitive Data

from cryptography.fernet import Fernet

# Imagine you're storing credit card info
key = Fernet.generate_key()
cipher = Fernet(key)

# Sensitive data
credit_card = "4532-1234-5678-9012"
ssn = "123-45-6789"

# Encrypt both
encrypted_cc = cipher.encrypt(credit_card.encode())
encrypted_ssn = cipher.encrypt(ssn.encode())

print("=== Encrypted (safe to store in database) ===")
print(f"Credit Card: {encrypted_cc.decode()[:50]}...")
print(f"SSN: {encrypted_ssn.decode()[:50]}...")

print("\n=== Decrypted (when authorized user needs it) ===")
print(f"Credit Card: {cipher.decrypt(encrypted_cc).decode()}")
print(f"SSN: {cipher.decrypt(encrypted_ssn).decode()}")

Python Example 3: Without the Key, You Can’t Decrypt

from cryptography.fernet import Fernet

# Alice encrypts a message
alice_key = Fernet.generate_key()
alice_cipher = Fernet(alice_key)
secret = alice_cipher.encrypt(b"Meet me at noon")

print(f"Encrypted message: {secret.decode()[:50]}...")

# Bob tries to decrypt with a DIFFERENT key
bob_key = Fernet.generate_key()  # Different key!
bob_cipher = Fernet(bob_key)

try:
    bob_cipher.decrypt(secret)
    print("Bob decrypted it!")
except Exception as e:
    print(f"❌ Bob can't decrypt it: Invalid key!")

# Alice can decrypt with her key
decrypted = alice_cipher.decrypt(secret).decode()
print(f"✅ Alice decrypted: {decrypted}")

Output:

Encrypted message: gAAAAABl...
❌ Bob can't decrypt it: Invalid key!
✅ Alice decrypted: Meet me at noon

How Secure Messaging Works

sequenceDiagram participant A as 👩 Alice participant B as 👨 Bob participant H as 🕵️ Hacker Note over A,B: Alice wants to send "Meet at 5pm" A->>A: Encrypt with shared key A->>B: Send: "xK9#mP2..." Note over H: Hacker intercepts H->>H: Sees "xK9#mP2..." H->>H: ❌ Can't read without key! B->>B: Decrypt with shared key Note over B: ✅ Reads: "Meet at 5pm"

When to Use Encryption

  • ✅ Storing sensitive data you need to read later (credit cards, personal info)
  • ✅ Sending private messages
  • ✅ Protecting files on your computer
  • ✅ Secure communication (HTTPS, email)

Side-by-Side Comparison

import hashlib
from cryptography.fernet import Fernet

message = "My secret data"

# ===== HASHING =====
hash_result = hashlib.sha256(message.encode()).hexdigest()
print("HASHING")
print(f"  Input:  {message}")
print(f"  Output: {hash_result[:40]}...")
print(f"  Can reverse? NO ❌")

# ===== ENCRYPTION =====
key = Fernet.generate_key()
cipher = Fernet(key)
encrypted = cipher.encrypt(message.encode())
decrypted = cipher.decrypt(encrypted).decode()

print("\nENCRYPTION")
print(f"  Input:     {message}")
print(f"  Encrypted: {encrypted.decode()[:40]}...")
print(f"  Decrypted: {decrypted}")
print(f"  Can reverse? YES ✅ (with the key)")

Quick Decision Guide

Ask yourself: “Do I need to get the original data back?”

flowchart TD A["🤔 What do you need to do
with the data?"] --> B{"Do you need the
original data back?"} B -->|"❌ No"| C["Use HASHING"] B -->|"✅ Yes"| D["Use ENCRYPTION"] C --> E["Examples:
• Password storage
• File checksums
• Data verification"] D --> F["Examples:
• Credit card storage
• Private messages
• Secure files"] style A fill:#f1f5f9,stroke:#64748b style B fill:#fef3c7,stroke:#d97706 style C fill:#dbeafe,stroke:#2563eb style D fill:#d1fae5,stroke:#059669 style E fill:#eff6ff,stroke:#2563eb style F fill:#ecfdf5,stroke:#059669
Your Answer Use This Example
No — I just need to verify Hashing Password login, file checksums
Yes — I need the original later Encryption Storing credit cards, private messages

Best Practices

For Hashing Passwords

Don’t use plain SHA-256 for passwords! Use a specialized password hashing library:

# Better for passwords: use bcrypt
# pip install bcrypt

import bcrypt

password = "MyPassword123"

# Hash with salt (automatic)
hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt())
print(f"Hashed: {hashed.decode()}")

# Verify later
if bcrypt.checkpw(password.encode(), hashed):
    print("✅ Password matches!")

Why bcrypt? It’s intentionally slow, making it harder for attackers to guess passwords.

For Encryption

  • 🔑 Keep your keys secret and secure
  • 🔄 Rotate keys periodically
  • 🔒 Use authenticated encryption (the examples above use Fernet, which includes authentication)

Summary

  Hashing Encryption
Direction One-way → Two-way ↔️
Key needed? No Yes
Reversible? No Yes (with key)
Output size Fixed (e.g., 64 chars) Varies with input
Main use Verify/identify Protect secrets
Examples Passwords, checksums Messages, files, data

Remember:

  • Hashing = fingerprint (can’t recreate the person)
  • Encryption = lockbox (can open with the key)