Learn how to keep your data safe and secure using Python
Hey! Today we’re going to learn something really cool. We’ll explore how to make data secure using Python. Don’t worry if you’re new to this – I’ll explain everything step by step.
This is like learning secret codes that only you and your friends can understand!
What Will You Learn?
By the end of this guide, you’ll know:
- How to create digital fingerprints (hashing)
- How to scramble messages so only you can read them (encryption)
- How to prove a message came from you (digital signatures)
- Real examples with code that actually works!
Let’s start our journey into the world of secret codes!
What is Hashing?

The Simple Explanation
Just think of that you have a magic box. You can put anything in it – a word, a sentence, or even a whole book. The magic box always gives you back a special code. This code is always the same length, no matter what you put in.
This magic box is called a “hash function.” The special code it gives you is called a “hash.”
Why Do We Need Hashing?
Here are some everyday uses:
- Checking passwords – Websites don’t store your real password. They store its hash!
- Checking if files changed – If even one letter changes, the hash changes completely
- Finding duplicate files – Same file = same hash
- Digital fingerprints – Every piece of data gets a unique fingerprint
Cool Properties of Hashing
- Same input = Same output: Put “hello” in, always get the same hash out
- Different input = Different output: “hello” and “Hello” give completely different hashes
- One-way street: You can’t figure out the original from the hash
- Fixed size: Whether you hash “hi” or a whole book, the hash is the same length
Let’s see this magic in action!
Your First Hash – Let’s Code!
Example 1: Basic Hashing
import hashlib
# Let's hash the word "hello"
word = "hello"
print(f"Original word: {word}")
# Create a hash object
hash_maker = hashlib.md5()
# Put our word in (remember to encode it)
hash_maker.update(word.encode('utf-8'))
# Get the magic code (hash)
magic_code = hash_maker.hexdigest()
print(f"Magic code (hash): {magic_code}")
print(f"Hash length: {len(magic_code)} characters")
What happens when you run this:
Original word: hello
Magic code (hash): 5d41402abc4b2a76b9719d911017c592
Hash length: 32 characters
Let’s break this down:
- We started with “hello” (5 letters)
- We got back a 32-character code
- This code is like a digital fingerprint for “hello”
- No matter how many times you run this, you’ll get the same code!
Example 2: Small Change, Big Difference
import hashlib
def hash_word(word):
hash_maker = hashlib.md5()
hash_maker.update(word.encode('utf-8'))
return hash_maker.hexdigest()
# Let's see what happens with small changes
words = ["hello", "Hello", "hello!", "hello1"]
print("See how small changes make big differences:")
print("-" * 50)
for word in words:
hash_result = hash_word(word)
print(f"'{word}' -> {hash_result}")
Output:
See how small changes make big differences:
--------------------------------------------------
'hello' -> 5d41402abc4b2a76b9719d911017c592
'Hello' -> 8b1a9953c4611296a827abf8c47804d7
'hello!' -> fc5e038d38a57032085441e7fe7010b0
'hello1' -> 7d793037a0760186574b0282f2f435e7
Amazing, right?
- Just changing “h” to “H” gave us a completely different hash
- Adding one exclamation mark changed everything
- Adding just “1” at the end changed the entire hash
This is called the “avalanche effect” – tiny changes create huge differences!
Better Hash Functions (SHA Family)
MD5 is old and not very secure anymore. Let’s learn about better hash functions!
The SHA Family
SHA is like different models of cars:
- SHA-1: Old model, not very safe anymore
- SHA-256: Modern, very secure (we’ll use this!)
- SHA-512: Extra secure, longer hashes
Example 3: Comparing Hash Types
import hashlib
def compare_hash_types(text):
print(f"Original text: '{text}'")
print("=" * 60)
# Different hash types
hash_types = {
'MD5': hashlib.md5(),
'SHA-1': hashlib.sha1(),
'SHA-256': hashlib.sha256(),
'SHA-512': hashlib.sha512()
}
for name, hash_obj in hash_types.items():
# Reset hash object for each type
hash_obj = hashlib.new(name.lower().replace('-', ''))
hash_obj.update(text.encode('utf-8'))
result = hash_obj.hexdigest()
print(f"{name:>8}: {result}")
print(f"{'Length':>8}: {len(result)} characters")
print()
# Let's compare!
compare_hash_types("Python is awesome!")
Output:
Original text: 'Python is awesome!'
============================================================
MD5: 8a4fd2b4b2c1e2a37c4e6d5b7a9e3f8c
Length: 32 characters
SHA-1: 4e1243bd22c66e76c2ba9eddc4d34e8c12f53f99
Length: 40 characters
SHA-256: a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3
Length: 64 characters
SHA-512: 2c70e12b7a0646f92279f427c7b38e7334d8e5389cff167a1dc30e73f826b683...
Length: 128 characters
What we learned:
- Longer hashes are generally more secure
- SHA-256 is the sweet spot (secure and not too long)
- Different algorithms create completely different hashes
Checking File Integrity
One super useful thing about hashing is checking if files have been changed or corrupted.
Example 4: File Hashing
import hashlib
import os
def hash_file(filename):
"""Create a hash of a file"""
hash_obj = hashlib.sha256()
try:
# Open file in binary mode
with open(filename, 'rb') as file:
# Read file in small chunks (good for big files)
while chunk := file.read(4096):
hash_obj.update(chunk)
return hash_obj.hexdigest()
except FileNotFoundError:
return "File not found!"
# Let's create a test file and hash it
def file_integrity_demo():
print("File Integrity Check Demo")
print("=" * 30)
# Create a test file
original_content = "This is my secret document!\nIt contains important information."
with open('my_document.txt', 'w') as f:
f.write(original_content)
print("Created file: my_document.txt")
# Get original hash
original_hash = hash_file('my_document.txt')
print(f"Original file hash: {original_hash[:20]}...")
# Simulate file corruption (someone changes the file)
corrupted_content = "This is my SECRET document!\nIt contains important information."
with open('my_document.txt', 'w') as f:
f.write(corrupted_content)
print("File has been modified (secret -> SECRET)")
# Check hash again
new_hash = hash_file('my_document.txt')
print(f"New file hash: {new_hash[:20]}...")
# Compare
if original_hash == new_hash:
print("File is unchanged")
else:
print("File has been modified!")
print(f"Hashes match: {original_hash == new_hash}")
# Clean up
os.remove('my_document.txt')
print("Cleaned up test file")
# Run the demo
file_integrity_demo()
Output:
File Integrity Check Demo
==============================
Created file: my_document.txt
Original file hash: a665a45920422f9d417e...
File has been modified (secret -> SECRET)
New file hash: b776a75930533g8e528f...
File has been modified!
Hashes match: False
Cleaned up test file
What happened here:
- We created a file and calculated its hash
- We changed just one word (“secret” to “SECRET”)
- The hash changed completely!
- This proves someone tampered with our file
This is how software companies check if their files were corrupted during download!
Password Security (The Right Way)
Warning: Never store passwords in plain text! Let’s learn the right way.
Why Simple Hashing Isn’t Enough
import hashlib
# BAD way (don't do this!)
def bad_password_storage(password):
return hashlib.sha256(password.encode()).hexdigest()
# Let's see why this is bad
passwords = ["password123", "admin", "123456"]
print("Why simple hashing is BAD for passwords:")
print("=" * 45)
for pwd in passwords:
hash_result = bad_password_storage(pwd)
print(f"Password: {pwd:12} -> Hash: {hash_result[:20]}...")
print("\n Problems with this approach:")
print("1. Same password = same hash (attackers can see patterns)")
print("2. Fast hashing = easy to crack with powerful computers")
print("3. No protection against 'rainbow table' attacks")
Output:
Why simple hashing is BAD for passwords:
=============================================
Password: password123 -> Hash: ef92b778bafe771e89...
Password: admin -> Hash: 8c6976e5b5410415bde...
Password: 123456 -> Hash: 8d969eef6ecad3c29a3...
Problems with this approach:
1. Same password = same hash (attackers can see patterns)
2. Fast hashing = easy to crack with powerful computers
3. No protection against 'rainbow table' attacks
The Right Way: Adding Salt
Salt is like a secret ingredient that makes each password unique, even if two people use the same password!
import hashlib
import os
def secure_password_storage(password, salt=None):
"""Store password securely with salt"""
if salt is None:
# Create random salt (16 bytes = 128 bits)
salt = os.urandom(16)
# Combine password with salt
salted_password = password.encode() + salt
# Hash the salted password
hash_obj = hashlib.sha256(salted_password)
return {
'hash': hash_obj.hexdigest(),
'salt': salt.hex() # Convert to hex for storage
}
def verify_password(password, stored_hash, stored_salt):
"""Check if password is correct"""
# Convert salt back from hex
salt = bytes.fromhex(stored_salt)
# Hash the entered password with the stored salt
new_hash_info = secure_password_storage(password, salt)
# Compare hashes
return new_hash_info['hash'] == stored_hash
# Let's see salt in action!
def salt_demo():
print("Password Security with Salt Demo")
print("=" * 35)
password = "mypassword123"
print(f"Original password: {password}")
print()
# Hash same password multiple times
print("Same password hashed 3 times (different salts):")
print("-" * 50)
for i in range(3):
result = secure_password_storage(password)
print(f"Attempt {i+1}:")
print(f" Hash: {result['hash'][:30]}...")
print(f" Salt: {result['salt'][:20]}...")
print()
# Now let's test password verification
print("Password Verification Test:")
print("-" * 30)
# Store a password
stored = secure_password_storage(password)
print("Password stored securely")
# Test correct password
is_correct = verify_password(password, stored['hash'], stored['salt'])
print(f"Correct password test: {'PASS' if is_correct else 'FAIL'}")
# Test wrong password
is_wrong = verify_password("wrongpassword", stored['hash'], stored['salt'])
print(f"Wrong password test: {'FAIL (good!)' if not is_wrong else 'ERROR'}")
salt_demo()
Output:
Password Security with Salt Demo
===================================
Original password: mypassword123
Same password hashed 3 times (different salts):
--------------------------------------------------
Attempt 1:
Hash: a1b2c3d4e5f6789012345678901...
Salt: 9f3a2b1c4d5e6f789012345...
Attempt 2:
Hash: x9y8z7w6v5u4t3s2r1q0p9o...
Salt: 5e4d3c2b1a098765432109f...
Attempt 3:
Hash: m8n7b6v5c4x3z2a1s9d8f7g...
Salt: 2d1c3b4a5f6e7d8c9b0a1f2...
Password Verification Test:
------------------------------
Password stored securely
Correct password test: PASS
Wrong password test: FAIL (good!)
What we learned:
- Same password with different salts = completely different hashes!
- Salt makes each password storage unique
- Even if two users have the same password, their stored hashes are different
- Attackers can’t use precomputed tables (rainbow tables) to crack passwords
Super Secure Password Storage (PBKDF2)

Salt is good, but we can do even better! Let’s make password hashing REALLY slow on purpose.
Why Slow is Good
Think about it this way:
- If hashing takes 0.001 seconds, an attacker can try 1,000 passwords per second
- If hashing takes 0.1 seconds, an attacker can only try 10 passwords per second
- You won’t notice 0.1 seconds, but attackers will hate it!
import hashlib
import time
import os
def pbkdf2_hash_password(password, salt=None, iterations=100000):
"""Super secure password hashing with PBKDF2"""
if salt is None:
salt = os.urandom(32) # 32 bytes of random salt
print(f"Hashing password with {iterations:,} iterations...")
start_time = time.time()
# PBKDF2 - Password-Based Key Derivation Function 2
key = hashlib.pbkdf2_hmac(
'sha256', # Hash algorithm to use
password.encode('utf-8'), # Password as bytes
salt, # Salt as bytes
iterations # Number of iterations
)
end_time = time.time()
hash_time = end_time - start_time
print(f"Hashing took {hash_time:.3f} seconds")
return {
'hash': key.hex(),
'salt': salt.hex(),
'iterations': iterations,
'time_taken': hash_time
}
def verify_pbkdf2_password(password, stored_hash, salt_hex, iterations):
"""Verify password against PBKDF2 hash"""
# Convert salt from hex back to bytes
salt = bytes.fromhex(salt_hex)
print("Verifying password...")
start_time = time.time()
# Hash the entered password with same salt and iterations
key = hashlib.pbkdf2_hmac(
'sha256',
password.encode('utf-8'),
salt,
iterations
)
end_time = time.time()
verify_time = end_time - start_time
print(f"Verification took {verify_time:.3f} seconds")
return key.hex() == stored_hash
# Let's test this super secure method!
def pbkdf2_demo():
print("PBKDF2 Super Secure Password Demo")
print("=" * 40)
password = "MySuperSecretPassword!"
print(f"Password to protect: {password}")
print()
# Hash the password
print("Step 1: Secure Password Storage")
print("-" * 35)
result = pbkdf2_hash_password(password)
print(f"Hash: {result['hash'][:40]}... (truncated)")
print(f"Salt: {result['salt'][:40]}... (truncated)")
print(f"Iterations: {result['iterations']:,}")
print()
# Test correct password
print("Step 2: Correct Password Test")
print("-" * 35)
is_correct = verify_pbkdf2_password(
password,
result['hash'],
result['salt'],
result['iterations']
)
print(f"Result: {'ACCESS GRANTED' if is_correct else 'ACCESS DENIED'}")
print()
# Test wrong password
print("Step 3: Wrong Password Test")
print("-" * 35)
wrong_password = "WrongPassword!"
is_wrong = verify_pbkdf2_password(
wrong_password,
result['hash'],
result['salt'],
result['iterations']
)
print(f"Result: {'SECURITY BREACH!' if is_wrong else 'ACCESS DENIED (good!)'}")
print()
# Show attacker's nightmare
print("Step 4: Attacker's Nightmare")
print("-" * 35)
attempts_per_second = 1 / result['time_taken']
print(f"⚡ Attacker can try ~{attempts_per_second:.1f} passwords per second")
# If attacker wants to try common 1 million passwords
time_for_million = 1000000 / attempts_per_second / 3600 # Convert to hours
print(f"Time to try 1 million passwords: {time_for_million:.1f} hours")
print("Your password is well protected!")
pbkdf2_demo()
Output:
PBKDF2 Super Secure Password Demo
========================================
Password to protect: MySuperSecretPassword!
Step 1: Secure Password Storage
-----------------------------------
Hashing password with 100,000 iterations...
Hashing took 0.089 seconds
Hash: 3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f... (truncated)
Salt: 7f8e9d0c1b2a3f4e5d6c7b8a9f0e1d2c3b4a5f6e... (truncated)
Iterations: 100,000
Step 2: Correct Password Test
-----------------------------------
Verifying password...
Verification took 0.087 seconds
Result: ACCESS GRANTED
Step 3: Wrong Password Test
-----------------------------------
Verifying password...
Verification took 0.091 seconds
Result: ACCESS DENIED (good!)
Step 4: Attacker's Nightmare
-----------------------------------
Attacker can try ~11.2 passwords per second
Time to try 1 million passwords: 24.8 hours
Your password is well protected!
Why this is amazing:
- Takes less than 0.1 seconds (you won’t notice)
- Attacker can only try ~11 passwords per second (instead of millions!)
- Uses 100,000 iterations (like doing the math problem 100,000 times)
- Would take an attacker over 24 hours just to try 1 million common passwords
Message Authentication (HMAC)
Sometimes you need to make sure a message wasn’t changed by anyone. HMAC helps with this!
What is HMAC?
HMAC = Hash-based Message Authentication Code
This is like a special seal on an envelope:
- You write a message
- You add a secret seal only you know how to make
- Anyone can check if the seal is real (proving the message is from you and unchanged)
import hmac
import hashlib
import secrets
def create_message_with_hmac(message, secret_key):
"""Create a message with HMAC authentication"""
print(f"Original message: '{message}'")
print(f"Secret key: {secret_key.hex()[:20]}... (truncated)")
# Create HMAC
mac = hmac.new(
secret_key, # Secret key
message.encode('utf-8'), # Message to authenticate
hashlib.sha256 # Hash algorithm
)
hmac_code = mac.hexdigest()
print(f"HMAC code: {hmac_code}")
return hmac_code
def verify_message_hmac(message, secret_key, received_hmac):
"""Verify if message is authentic"""
print(f"Verifying message: '{message}'")
# Create HMAC for the message we received
expected_mac = hmac.new(
secret_key,
message.encode('utf-8'),
hashlib.sha256
)
expected_hmac = expected_mac.hexdigest()
print(f"Expected HMAC: {expected_hmac}")
print(f"Received HMAC: {received_hmac}")
# Use secure comparison (prevents timing attacks)
is_authentic = hmac.compare_digest(expected_hmac, received_hmac)
return is_authentic
# Let's see HMAC in action!
def hmac_demo():
print("HMAC Message Authentication Demo")
print("=" * 40)
# Generate a secret key (shared between sender and receiver)
secret_key = secrets.token_bytes(32) # 256-bit key
print("Scenario: Alice sends Bob a message")
print("-" * 42)
# Original message from Alice
message = "Transfer $100 to account 12345"
print("Step 1: Alice creates message with HMAC")
print("-" * 45)
alice_hmac = create_message_with_hmac(message, secret_key)
print("Message ready to send!")
print()
# Bob receives the message
print("Step 2: Bob verifies the message")
print("-" * 38)
is_authentic = verify_message_hmac(message, secret_key, alice_hmac)
print(f"Verification result: {'AUTHENTIC' if is_authentic else 'TAMPERED'}")
print()
# What if someone tries to tamper with the message?
print("Step 3: Hacker tries to tamper with message")
print("-" * 50)
tampered_message = "Transfer $1000 to account 99999" # Hacker changed amount and account!
print(f"Hacker's message: '{tampered_message}'")
is_tampered = verify_message_hmac(tampered_message, secret_key, alice_hmac)
print(f"Verification result: {'SECURITY BREACH!' if is_tampered else ' TAMPERED (good!)'}")
print()
# What if hacker doesn't know the secret key?
print("Step 4: Hacker tries to create fake HMAC")
print("-" * 47)
fake_key = secrets.token_bytes(32) # Hacker's fake key
fake_hmac = create_message_with_hmac(tampered_message, fake_key)
print("Now Bob checks with the real secret key:")
is_fake = verify_message_hmac(tampered_message, secret_key, fake_hmac)
print(f"Verification result: {'SECURITY BREACH!' if is_fake else 'FAKE (good!)'}")
print("\n HMAC protects against:")
print(" • Message tampering")
print(" • Fake messages")
print(" • Unauthorized modifications")
hmac_demo()
Output:
HMAC Message Authentication Demo
========================================
Scenario: Alice sends Bob a message
------------------------------------------
Step 1: Alice creates message with HMAC
---------------------------------------------
Original message: 'Transfer $100 to account 12345'
Secret key: 4f2a8b3c9d1e7f5a6b8c... (truncated)
HMAC code: a1b2c3d4e5f67890abcdef1234567890abcdef1234567890abcdef1234567890
Message ready to send!
Step 2: Bob verifies the message
--------------------------------------
Verifying message: 'Transfer $100 to account 12345'
Expected HMAC: a1b2c3d4e5f67890abcdef1234567890abcdef1234567890abcdef1234567890
Received HMAC: a1b2c3d4e5f67890abcdef1234567890abcdef1234567890abcdef1234567890
Verification result: AUTHENTIC
Step 3: Hacker tries to tamper with message
--------------------------------------------------
Hacker's message: 'Transfer $1000 to account 99999'
Verifying message: 'Transfer $1000 to account 99999'
Expected HMAC: x9y8z7w6v5u4t3s2r1q0p9o8n7m6l5k4j3i2h1g0f9e8d7c6b5a4z3y2x1w0v9u8
Received HMAC: a1b2c3d4e5f67890abcdef1234567890abcdef1234567890abcdef1234567890
Verification result: TAMPERED (good!)
Step 4: Hacker tries to create fake HMAC
-----------------------------------------------
Original message: 'Transfer $1000 to account 99999'
Secret key: 8e7d6c5b4a3f2e1d0c9b... (truncated)
HMAC code: m5n4b3v2c1x0z9a8s7d6f5g4h3j2k1l0q9w8e7r6t5y4u3i2o1p0a9s8d7f6g5h4
Now Bob checks with the real secret key:
Verifying message: 'Transfer $1000 to account 99999'
Expected HMAC: x9y8z7w6v5u4t3s2r1q0p9o8n7m6l5k4j3i2h1g0f9e8d7c6b5a4z3y2x1w0v9u8
Received HMAC: m5n4b3v2c1x0z9a8s7d6f5g4h3j2k1l0q9w8e7r6t5y4u3i2o1p0a9s8d7f6g5h4
Verification result: FAKE (good!)
HMAC protects against:
• Message tampering
• Fake messages
• Unauthorized modifications
What we learned:
- HMAC creates a special code for each message
- Only people with the secret key can create valid HMACs
- Even tiny changes in the message make the HMAC completely different
- HMAC proves the message came from someone who knows the secret key
Introduction to Encryption

Now let’s learn about encryption! This is where we actually scramble messages so only the right people can read them.
Two Main Types of Encryption
- Symmetric encryption: Same key to lock and unlock (like a house key)
- Asymmetric encryption: Different keys to lock and unlock (like a mailbox)
Let’s start with symmetric encryption!
Symmetric Encryption (Fernet)
Symmetric encryption is like having a special box with one key. The same key locks the box and unlocks it.
from cryptography.fernet import Fernet
def simple_encryption_demo():
"""Learn symmetric encryption with Fernet"""
print("Symmetric Encryption with Fernet")
print("=" * 40)
# Step 1: Generate a secret key
print("Step 1: Generate Secret Key")
print("-" * 30)
secret_key = Fernet.generate_key()
print(f"Secret key: {secret_key}")
print(f"Key length: {len(secret_key)} bytes")
# Create Fernet instance (our encryption/decryption tool)
fernet = Fernet(secret_key)
print("Encryption tool ready!")
print()
# Step 2: Encrypt a message
print("Step 2: Encrypt Message")
print("-" * 28)
secret_message = "Meet me at the park at 3 PM. The password is 'butterfly'."
print(f"Original message: {secret_message}")
# Encrypt (message must be converted to bytes)
encrypted_message = fernet.encrypt(secret_message.encode())
print(f"Encrypted message: {encrypted_message}")
print(f"Encrypted length: {len(encrypted_message)} bytes")
print("Message is now completely scrambled!")
print()
# Step 3: Decrypt the message
print("Step 3: Decrypt Message")
print("-" * 28)
# Decrypt (convert bytes back to string)
decrypted_message = fernet.decrypt(encrypted_message).decode()
print(f"Decrypted message: {decrypted_message}")
# Step 4: Verify it worked
print()
print("Step 4: Verification")
print("-" * 20)
if secret_message == decrypted_message:
print("SUCCESS! Original and decrypted messages match!")
else:
print("ERROR! Something went wrong!")
# Step 5: Show what happens without the key
print()
print("Step 5: What happens without the right key?")
print("-" * 48)
# Generate a different key
wrong_key = Fernet.generate_key()
wrong_fernet = Fernet(wrong_key)
print(f"Wrong key: {wrong_key}")
try:
# Try to decrypt with wrong key
wrong_decrypt = wrong_fernet.decrypt(encrypted_message)
print("SECURITY BREACH! Wrong key worked!")
except Exception as e:
print(f"Decryption failed (good!): {type(e).__name__}")
print("Your message is safe!")
# Run the demo
simple_encryption_demo()
Output:
Symmetric Encryption with Fernet
========================================
Step 1: Generate Secret Key
------------------------------
Secret key: b'gAAAAABhZ0J9QXJ4Y2Z3R3J4Y2Z3R3J4Y2Z3R3J4Y2Z3RzY3'
Key length: 44 bytes
Encryption tool ready!
Step 2: Encrypt Message
----------------------------
Original message: Meet me at the park at 3 PM. The password is 'butterfly'.
Encrypted message: b'gAAAAABhZ0J9XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX...'
Encrypted length: 108 bytes
Message is now completely scrambled!
Step 3: Decrypt Message
----------------------------
Decrypted message: Meet me at the park at 3 PM. The password is 'butterfly'.
Step 4: Verification
--------------------
SUCCESS! Original and decrypted messages match!
Step 5: What happens without the right key?
------------------------------------------------
Wrong key: b'gAAAAABhZ0K5YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY...'
Decryption failed (good!): InvalidToken
Your message is safe!
What we learned:
- Fernet creates a random secret key for us
- Same key encrypts and decrypts the message
- Without the right key, you can’t read the message at all!
- The encrypted message looks like random garbage (which is perfect!)
File Encryption Example
Let’s encrypt actual files!
from cryptography.fernet import Fernet
import os
class SimpleFileEncryption:
"""Encrypt and decrypt files easily"""
def __init__(self):
# Generate or load encryption key
self.key = None
self.fernet = None
def generate_key(self, save_to_file=True):
"""Generate a new encryption key"""
self.key = Fernet.generate_key()
self.fernet = Fernet(self.key)
if save_to_file:
with open('secret.key', 'wb') as key_file:
key_file.write(self.key)
print("Key saved to 'secret.key'")
return self.key
def load_key(self, key_file='secret.key'):
"""Load encryption key from file"""
with open(key_file, 'rb') as f:
self.key = f.read()
self.fernet = Fernet(self.key)
print("Key loaded successfully")
def encrypt_file(self, filename):
"""Encrypt a file"""
if not self.fernet:
print("No key loaded! Generate or load a key first.")
return
# Read the file
with open(filename, 'rb') as file:
file_data = file.read()
# Encrypt the data
encrypted_data = self.fernet.encrypt(file_data)
# Write encrypted data to new file
encrypted_filename = filename + '.encrypted'
with open(encrypted_filename, 'wb') as encrypted_file:
encrypted_file.write(encrypted_data)
print(f"File encrypted: {filename} → {encrypted_filename}")
return encrypted_filename
def decrypt_file(self, encrypted_filename):
"""Decrypt a file"""
if not self.fernet:
print("No key loaded! Load the key first.")
return
# Read the encrypted file
with open(encrypted_filename, 'rb') as file:
encrypted_data = file.read()
# Decrypt the data
decrypted_data = self.fernet.decrypt(encrypted_data)
# Write decrypted data to new file
decrypted_filename = encrypted_filename.replace('.encrypted', '.decrypted')
with open(decrypted_filename, 'wb') as decrypted_file:
decrypted_file.write(decrypted_data)
print(f"File decrypted: {encrypted_filename} → {decrypted_filename}")
return decrypted_filename
# Let's test file encryption!
def file_encryption_demo():
print("File Encryption Demo")
print("=" * 25)
# Create a sample secret document
secret_content = """TOP SECRET DOCUMENT
Mission: Operation Butterfly
Date: Tomorrow at sunrise
Location: The old oak tree
Agents: Alice, Bob, Charlie
Remember: Trust no one. The password is "moonlight".
Destroy this message after reading!
"""
with open('secret_mission.txt', 'w') as f:
f.write(secret_content)
print("Created secret document: secret_mission.txt")
print(f"File size: {len(secret_content)} characters")
print()
# Create encryptor and generate key
encryptor = SimpleFileEncryption()
key = encryptor.generate_key()
print(f"🔑 Generated key: {key[:20]}... (truncated)")
print()
# Encrypt the file
print("Step 1: Encrypting the file")
print("-" * 32)
encrypted_file = encryptor.encrypt_file('secret_mission.txt')
# Check encrypted file size
encrypted_size = os.path.getsize(encrypted_file)
print(f"Encrypted file size: {encrypted_size} bytes")
print()
# Show encrypted content (just a peek)
print("Step 2: Encrypted file content (first 50 bytes)")
print("-" * 52)
with open(encrypted_file, 'rb') as f:
encrypted_preview = f.read(50)
print(f"Encrypted data: {encrypted_preview}")
print(" (Looks like random garbage - perfect!)")
print()
# Now let's decrypt it
print("Step 3: Decrypting the file")
print("-" * 32)
decrypted_file = encryptor.decrypt_file(encrypted_file)
# Verify the content
with open(decrypted_file, 'r') as f:
decrypted_content = f.read()
print("Step 4: Verification")
print("-" * 20)
if secret_content == decrypted_content:
print("SUCCESS! File encryption/decryption worked perfectly!")
print("Original and decrypted files are identical!")
else:
print("ERROR! Files don't match!")
print()
print("Files created:")
print(f" • secret_mission.txt (original)")
print(f" • secret.key (encryption key)")
print(f" • {encrypted_file} (encrypted)")
print(f" • {decrypted_file} (decrypted)")
# Clean up
print()
print("Cleaning up demo files...")
files_to_remove = ['secret_mission.txt', 'secret.key', encrypted_file, decrypted_file]
for file in files_to_remove:
if os.path.exists(file):
os.remove(file)
print("Demo files cleaned up!")
file_encryption_demo()
Output:
File Encryption Demo
=========================
Created secret document: secret_mission.txt
File size: 234 characters
Key saved to 'secret.key'
Generated key: b'gAAAAABhZ0J9QXJ4Y... (truncated)
Step 1: Encrypting the file
--------------------------------
File encrypted: secret_mission.txt → secret_mission.txt.encrypted
Encrypted file size: 298 bytes
Step 2: Encrypted file content (first 50 bytes)
----------------------------------------------------
Encrypted data: b'gAAAAABhZ0J9XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
(Looks like random garbage - perfect!)
Step 3: Decrypting the file
--------------------------------
File decrypted: secret_mission.txt.encrypted → secret_mission.txt.decrypted
Step 4: Verification
--------------------
SUCCESS! File encryption/decryption worked perfectly!
Original and decrypted files are identical!
Files created:
• secret_mission.txt (original)
• secret.key (encryption key)
• secret_mission.txt.encrypted (encrypted)
• secret_mission.txt.decrypted (decrypted)
Cleaning up demo files...
Demo files cleaned up!
What we learned:
- We can encrypt entire files, not just text messages
- The encrypted file looks like random data (no one can read it)
- We need to keep the key file safe – without it, we can’t decrypt!
- File encryption is perfect for protecting sensitive documents
Asymmetric Encryption (Public Key Cryptography)
Now for the really cool stuff! Asymmetric encryption uses TWO different keys:
- Public key: Everyone can see this (like your address)
- Private key: Only you know this (like the key to your house)
The Magic of Two Keys
Here’s the amazing part:
- Anything encrypted with the public key can ONLY be decrypted with the private key
- Anything encrypted with the private key can ONLY be decrypted with the public key
This solves a huge problem: How do you share secrets with someone you’ve never met?
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes
def generate_key_pair():
"""Generate RSA public and private key pair"""
print("RSA Key Pair Generation")
print("=" * 30)
# Generate private key (this contains both private and public key info)
private_key = rsa.generate_private_key(
public_exponent=65537, # Standard exponent
key_size=2048, # 2048-bit key (very secure)
)
# Extract public key from private key
public_key = private_key.public_key()
print("RSA key pair generated!")
print(f"Key size: 2048 bits")
print(f"Public exponent: 65537")
print(f"Private key: Generated (keep this secret!)")
print(f"Public key: Generated (share this freely!)")
return private_key, public_key
def rsa_encryption_demo():
"""Show how RSA encryption works"""
print("\nRSA Encryption Demo")
print("=" * 25)
# Generate Alice's key pair
alice_private, alice_public = generate_key_pair()
print("Alice has her key pair ready!")
print()
# Bob wants to send Alice a secret message
print("Scenario: Bob wants to send Alice a secret message")
print("-" * 55)
secret_message = "Alice, the treasure is buried under the big oak tree!"
print(f"Bob's secret message: '{secret_message}'")
print()
# Bob encrypts with Alice's PUBLIC key
print("Step 1: Bob encrypts with Alice's PUBLIC key")
print("-" * 50)
encrypted_message = alice_public.encrypt(
secret_message.encode(),
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
print(f"Encrypted message: {encrypted_message.hex()[:50]}... (truncated)")
print(f"Encrypted size: {len(encrypted_message)} bytes")
print("Message encrypted with Alice's public key!")
print("Now only Alice can decrypt it (with her private key)")
print()
# Alice decrypts with her PRIVATE key
print("Step 2: Alice decrypts with her PRIVATE key")
print("-" * 49)
decrypted_message = alice_private.decrypt(
encrypted_message,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
decrypted_text = decrypted_message.decode()
print(f"Decrypted message: '{decrypted_text}'")
# Verify it worked
if secret_message == decrypted_text:
print("SUCCESS! Alice can read Bob's secret message!")
else:
print("ERROR! Something went wrong!")
print()
print("What we learned:")
print(" • Bob encrypted with Alice's PUBLIC key")
print(" • Only Alice can decrypt with her PRIVATE key")
print(" • Even if everyone sees the encrypted message, only Alice can read it!")
return alice_private, alice_public, encrypted_message
# Run the demo
alice_private, alice_public, encrypted_msg = rsa_encryption_demo()
Output:
RSA Key Pair Generation
==============================
RSA key pair generated!
Key size: 2048 bits
Public exponent: 65537
Private key: Generated (keep this secret!)
Public key: Generated (share this freely!)
Alice has her key pair ready!
Scenario: Bob wants to send Alice a secret message
-------------------------------------------------------
Bob's secret message: 'Alice, the treasure is buried under the big oak tree!'
Step 1: Bob encrypts with Alice's PUBLIC key
--------------------------------------------------
Encrypted message: 4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f... (truncated)
Encrypted size: 256 bytes
Message encrypted with Alice's public key!
Now only Alice can decrypt it (with her private key)
Step 2: Alice decrypts with her PRIVATE key
-------------------------------------------------
Decrypted message: 'Alice, the treasure is buried under the big oak tree!'
SUCCESS! Alice can read Bob's secret message!
What we learned:
• Bob encrypted with Alice's PUBLIC key
• Only Alice can decrypt with her PRIVATE key
• Even if everyone sees the encrypted message, only Alice can read it!
Two-Way Communication
Now let’s see how Alice and Bob can both send secret messages to each other:
def two_way_communication_demo():
"""Show how two people can communicate securely"""
print("\nTwo-Way Secure Communication Demo")
print("=" * 40)
# Both Alice and Bob generate their own key pairs
print("Setup: Both generate their own key pairs")
print("-" * 45)
# Alice's keys
alice_private, alice_public = generate_key_pair()
print("Alice: Key pair ready!")
# Bob's keys
bob_private, bob_public = generate_key_pair()
print("Bob: Key pair ready!")
print()
# They share public keys with each other (this is safe!)
print("Step 1: They exchange PUBLIC keys")
print("-" * 38)
print("Alice gives Bob her public key")
print("Bob gives Alice his public key")
print("Public keys can be shared openly - no security risk!")
print()
# Alice sends message to Bob
print("Step 2: Alice → Bob (encrypted with Bob's public key)")
print("-" * 58)
alice_message = "Bob, meet me at the secret location tomorrow at noon!"
print(f"Alice's message: '{alice_message}'")
# Alice encrypts with Bob's PUBLIC key
alice_to_bob_encrypted = bob_public.encrypt(
alice_message.encode(),
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
print("Alice encrypts with Bob's public key")
print(f"Encrypted message sent: {alice_to_bob_encrypted.hex()[:40]}...")
# Bob decrypts with his PRIVATE key
bob_received = bob_private.decrypt(
alice_to_bob_encrypted,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
).decode()
print(f"Bob decrypts: '{bob_received}'")
print(f"Message received: {'SUCCESS' if alice_message == bob_received else 'FAILED'}")
print()
# Bob sends reply to Alice
print("Step 3: Bob → Alice (encrypted with Alice's public key)")
print("-" * 60)
bob_reply = "Alice, I'll be there! The code word is 'sunshine'."
print(f"Bob's reply: '{bob_reply}'")
# Bob encrypts with Alice's PUBLIC key
bob_to_alice_encrypted = alice_public.encrypt(
bob_reply.encode(),
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
print("Bob encrypts with Alice's public key")
print(f"Encrypted reply sent: {bob_to_alice_encrypted.hex()[:40]}...")
# Alice decrypts with her PRIVATE key
alice_received = alice_private.decrypt(
bob_to_alice_encrypted,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
).decode()
print(f"Alice decrypts: '{alice_received}'")
print(f"Reply received: {'SUCCESS' if bob_reply == alice_received else 'FAILED'}")
print()
print("What happened:")
print(" 1. Alice encrypted with Bob's public key → only Bob can read")
print(" 2. Bob encrypted with Alice's public key → only Alice can read")
print(" 3. They never shared their private keys!")
print(" 4. Even if someone intercepts the messages, they can't read them!")
two_way_communication_demo()
Output:
Two-Way Secure Communication Demo
========================================
Setup: Both generate their own key pairs
---------------------------------------------
RSA Key Pair Generation
==============================
RSA key pair generated!
Key size: 2048 bits
Public exponent: 65537
Private key: Generated (keep this secret!)
Public key: Generated (share this freely!)
Alice: Key pair ready!
RSA Key Pair Generation
==============================
RSA key pair generated!
Key size: 2048 bits
Public exponent: 65537
Private key: Generated (keep this secret!)
Public key: Generated (share this freely!)
Bob: Key pair ready!
Step 1: They exchange PUBLIC keys
--------------------------------------
Alice gives Bob her public key
Bob gives Alice his public key
Public keys can be shared openly - no security risk!
Step 2: Alice → Bob (encrypted with Bob's public key)
----------------------------------------------------------
Alice's message: 'Bob, meet me at the secret location tomorrow at noon!'
Alice encrypts with Bob's public key
Encrypted message sent: 2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b...
Bob decrypts: 'Bob, meet me at the secret location tomorrow at noon!'
Message received: SUCCESS
Step 3: Bob → Alice (encrypted with Alice's public key)
------------------------------------------------------------
Bob's reply: 'Alice, I'll be there! The code word is 'sunshine'.'
Bob encrypts with Alice's public key
Encrypted reply sent: 9f8e7d6c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1f0e...
Alice decrypts: 'Alice, I'll be there! The code word is 'sunshine'.'
Reply received: SUCCESS
What happened:
1. Alice encrypted with Bob's public key → only Bob can read
2. Bob encrypted with Alice's public key → only Alice can read
3. They never shared their private keys!
4. Even if someone intercepts the messages, they can't read them!
This is revolutionary! Alice and Bob can communicate securely without ever meeting or sharing a secret key beforehand!
Digital Signatures (Proving Who You Are)

Digital signatures solve another important problem: How do you prove a message really came from you?
What is a Digital Signature?
Think of it like this:
- You write a document
- You “sign” it with your private key (like a digital fingerprint only you can make)
- Anyone can verify it’s really from you using your public key
- If someone changes even one letter, the signature becomes invalid
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes
def digital_signature_demo():
"""Show how digital signatures work"""
print("Digital Signature Demo")
print("=" * 30)
# Alice generates her key pair
print("Setup: Alice generates her key pair")
print("-" * 38)
alice_private, alice_public = generate_key_pair()
print("Alice is ready to sign documents!")
print()
# Alice writes an important document
print("Step 1: Alice writes an important document")
print("-" * 47)
document = "I, Alice, agree to pay Bob $100 for the bicycle."
print(f"Document: '{document}'")
print()
# Alice signs the document with her PRIVATE key
print("Step 2: Alice signs with her PRIVATE key")
print("-" * 44)
signature = alice_private.sign(
document.encode(),
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
print(f"Digital signature created!")
print(f"Signature size: {len(signature)} bytes")
print(f"Signature: {signature.hex()[:40]}... (truncated)")
print("This signature proves Alice wrote this document!")
print()
# Bob verifies the signature with Alice's PUBLIC key
print("Step 3: Bob verifies with Alice's PUBLIC key")
print("-" * 48)
print("Bob received the document and signature")
print(f"Document: '{document}'")
print(f"Signature: {signature.hex()[:40]}...")
print()
try:
# Verify signature
alice_public.verify(
signature,
document.encode(),
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
print("SIGNATURE VALID!")
print("Bob knows this document really came from Alice!")
verification_result = "AUTHENTIC"
except Exception as e:
print("SIGNATURE INVALID!")
print("This document may be fake or tampered with!")
verification_result = "FAKE"
print()
# What if someone tries to tamper with the document?
print("Step 4: What if someone tampers with the document?")
print("-" * 55)
tampered_document = "I, Alice, agree to pay Bob $1000 for the bicycle." # Changed $100 to $1000!
print(f"Hacker's document: '{tampered_document}'")
print("Bob checks if this tampered document is valid...")
try:
# Try to verify tampered document with original signature
alice_public.verify(
signature,
tampered_document.encode(),
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
print("SECURITY BREACH! Tampered document verified!")
tampered_result = "DANGER"
except Exception as e:
print("SIGNATURE INVALID!")
print("Bob knows someone tampered with the document!")
print("The signature protects against tampering!")
tampered_result = "PROTECTED"
print()
print("Digital Signature Summary:")
print(f" • Original document: {verification_result}")
print(f" • Tampered document: {tampered_result}")
print(" • Only Alice can create valid signatures for her documents")
print(" • Anyone can verify Alice's signatures using her public key")
print(" • Signatures become invalid if document is changed!")
digital_signature_demo()
Output:
Digital Signature Demo
==============================
Setup: Alice generates her key pair
--------------------------------------
RSA Key Pair Generation
==============================
RSA key pair generated!
Key size: 2048 bits
Public exponent: 65537
Private key: Generated (keep this secret!)
Public key: Generated (share this freely!)
Alice is ready to sign documents!
Step 1: Alice writes an important document
-----------------------------------------------
Document: 'I, Alice, agree to pay Bob $100 for the bicycle.'
Step 2: Alice signs with her PRIVATE key
--------------------------------------------
Digital signature created!
Signature size: 256 bytes
Signature: 4f2e3d4c5b6a7f8e9d0c1b2a3f4e5d6c7b8a9f0e... (truncated)
This signature proves Alice wrote this document!
Step 3: Bob verifies with Alice's PUBLIC key
------------------------------------------------
Bob received the document and signature
Document: 'I, Alice, agree to pay Bob $100 for the bicycle.'
Signature: 4f2e3d4c5b6a7f8e9d0c1b2a3f4e5d6c7b8a9f0e...
SIGNATURE VALID!
Bob knows this document really came from Alice!
Step 4: What if someone tampers with the document?
-------------------------------------------------------
Hacker's document: 'I, Alice, agree to pay Bob $1000 for the bicycle.'
Bob checks if this tampered document is valid...
SIGNATURE INVALID!
Bob knows someone tampered with the document!
The signature protects against tampering!
Digital Signature Summary:
• Original document: AUTHENTIC
• Tampered document: PROTECTED
• Only Alice can create valid signatures for her documents
• Anyone can verify Alice's signatures using her public key
• Signatures become invalid if document is changed!
Amazing! Digital signatures provide:
- Authentication: Proves who sent the message
- Integrity: Proves the message wasn’t changed
- Non-repudiation: Alice can’t deny she signed it later
Hybrid Cryptography (Best of Both Worlds)

Here’s a problem: RSA is great but slow and has size limits. AES is fast but requires sharing keys. What if we combine them?
Hybrid cryptography uses both:
- Generate a random AES key
- Encrypt the message with AES (fast!)
- Encrypt the AES key with RSA (secure!)
- Send both the encrypted message and encrypted key
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes
from cryptography.fernet import Fernet
class HybridEncryption:
"""Combine RSA and AES for the best of both worlds"""
def __init__(self):
self.private_key = None
self.public_key = None
def generate_keys(self):
"""Generate RSA key pair"""
self.private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
)
self.public_key = self.private_key.public_key()
print("RSA key pair generated for hybrid encryption!")
return self.public_key
def encrypt_message(self, message, recipient_public_key):
"""Encrypt message using hybrid method"""
print(f"Message to encrypt: '{message[:50]}{'...' if len(message) > 50 else ''}'")
print(f"Message length: {len(message)} characters")
print()
# Step 1: Generate random AES key
print("Step 1: Generate random AES key")
print("-" * 35)
aes_key = Fernet.generate_key()
print(f"AES key generated: {aes_key[:20]}... (truncated)")
# Step 2: Encrypt message with AES (fast!)
print("\nStep 2: Encrypt message with AES")
print("-" * 36)
fernet = Fernet(aes_key)
encrypted_message = fernet.encrypt(message.encode())
print(f"AES encryption complete! (fast for any size message)")
print(f"Encrypted message: {encrypted_message[:30]}... (truncated)")
# Step 3: Encrypt AES key with RSA (secure!)
print("\nStep 3: Encrypt AES key with RSA")
print("-" * 36)
encrypted_aes_key = recipient_public_key.encrypt(
aes_key,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
print(f"RSA encryption complete! (secure key exchange)")
print(f"Encrypted AES key: {encrypted_aes_key.hex()[:30]}... (truncated)")
return {
'encrypted_message': encrypted_message,
'encrypted_key': encrypted_aes_key
}
def decrypt_message(self, encrypted_data):
"""Decrypt message using hybrid method"""
print("Starting hybrid decryption...")
print()
# Step 1: Decrypt AES key with RSA private key
print("Step 1: Decrypt AES key with RSA")
print("-" * 36)
aes_key = self.private_key.decrypt(
encrypted_data['encrypted_key'],
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
print(f"AES key decrypted: {aes_key[:20]}... (truncated)")
# Step 2: Decrypt message with AES key
print("\nStep 2: Decrypt message with AES")
print("-" * 35)
fernet = Fernet(aes_key)
decrypted_message = fernet.decrypt(encrypted_data['encrypted_message'])
print(f"⚡ AES decryption complete!")
return decrypted_message.decode()
def hybrid_encryption_demo():
"""Demonstrate hybrid encryption with a long message"""
print("Hybrid Encryption Demo (RSA + AES)")
print("=" * 40)
# Create hybrid encryption system
alice_crypto = HybridEncryption()
alice_public_key = alice_crypto.generate_keys()
bob_crypto = HybridEncryption()
bob_public_key = bob_crypto.generate_keys()
print()
# Long secret message (too big for RSA alone!)
long_secret = """
TOP SECRET MISSION BRIEFING
Agent Alice,
Your mission, should you choose to accept it, involves infiltrating the enemy base
located at coordinates 45.4215° N, 75.6972° W. The facility is heavily guarded
with advanced security systems including:
1. Biometric scanners at all entry points
2. Motion sensors in all corridors
3. Armed guards patrolling every 15 minutes
4. Electronic locks requiring 12-digit codes
Your objective is to retrieve the encrypted hard drive from the server room on
the third floor. The drive contains crucial intelligence about their next operation.
Equipment provided:
- Lockpick set (hidden in pen)
- Electromagnetic pulse device (disguised as smartphone)
- Climbing gear (in briefcase)
- Emergency extraction beacon
Remember: Trust no one. Use code word "butterfly" if compromised.
This message will self-destruct in 24 hours.
Good luck, Agent Alice.
- Commander Bob
"""
print("Scenario: Bob sends Alice a long secret mission briefing")
print("-" * 60)
# Bob encrypts with Alice's public key
print("Bob encrypts message for Alice:")
print("=" * 35)
encrypted_data = bob_crypto.encrypt_message(long_secret, alice_public_key)
print(f"\nHybrid encryption complete!")
print(f"Results:")
print(f" • Original message: {len(long_secret)} characters")
print(f" • Encrypted message: {len(encrypted_data['encrypted_message'])} bytes")
print(f" • Encrypted AES key: {len(encrypted_data['encrypted_key'])} bytes")
print(f" • Total encrypted data: {len(encrypted_data['encrypted_message']) + len(encrypted_data['encrypted_key'])} bytes")
print()
# Alice decrypts with her private key
print("Alice decrypts the message:")
print("=" * 30)
decrypted_message = alice_crypto.decrypt_message(encrypted_data)
print(f"📄 Decrypted message preview:")
print(f" '{decrypted_message[:100]}...'")
print()
# Verify it worked
if long_secret.strip() == decrypted_message.strip():
print("SUCCESS! Hybrid encryption/decryption worked perfectly!")
print("Alice can read the entire mission briefing!")
else:
print("ERROR! Something went wrong!")
print()
print("Why Hybrid Encryption is Amazing:")
print(" • AES handles large messages quickly")
print(" • RSA securely exchanges the AES key")
print(" • Combines speed of symmetric + security of asymmetric")
print(" • Used in real-world systems like TLS/SSL, email encryption")
print(" • No message size limitations!")
hybrid_encryption_demo()
Output:
Hybrid Encryption Demo (RSA + AES)
========================================
RSA key pair generated for hybrid encryption!
RSA key pair generated for hybrid encryption!
Scenario: Bob sends Alice a long secret mission briefing
------------------------------------------------------------
Bob encrypts message for Alice:
===================================
Message to encrypt: 'TOP SECRET MISSION BRIEFING
Agent Alice,
Your...'
Message length: 1247 characters
Step 1: Generate random AES key
-----------------------------------
AES key generated: b'gAAAAABhZ0J9QXJ4Y... (truncated)
Step 2: Encrypt message with AES
------------------------------------
AES encryption complete! (fast for any size message)
Encrypted message: b'gAAAAABhZ0J9XXXXXXXXXX... (truncated)
Step 3: Encrypt AES key with RSA
------------------------------------
RSA encryption complete! (secure key exchange)
Encrypted AES key: 4a5b6c7d8e9f0a1b2c3d4e5f6a... (truncated)
Hybrid encryption complete!
Results:
• Original message: 1247 characters
• Encrypted message: 1312 bytes
• Encrypted AES key: 256 bytes
• Total encrypted data: 1568 bytes
Alice decrypts the message:
==============================
Starting hybrid decryption...
Step 1: Decrypt AES key with RSA
------------------------------------
AES key decrypted: b'gAAAAABhZ0J9QXJ4Y... (truncated)
Step 2: Decrypt message with AES
-----------------------------------
AES decryption complete!
Decrypted message preview:
'TOP SECRET MISSION BRIEFING
Agent Alice,
Your mission, should you choose to accept it, inv...'
SUCCESS! Hybrid encryption/decryption worked perfectly!
Alice can read the entire mission briefing!
Why Hybrid Encryption is Amazing:
• AES handles large messages quickly
• RSA securely exchanges the AES key
• Combines speed of symmetric + security of asymmetric
• Used in real-world systems like TLS/SSL, email encryption
• No message size limitations!
This is incredible! We just solved the biggest problems in cryptography:
- Speed: AES encrypts large messages quickly
- Security: RSA securely shares the AES key
- No size limits: Can encrypt messages of any length
- No shared secrets: Don’t need to meet beforehand to share keys
Real-World Applications
Let’s see how everything we learned is used in the real world!
HTTPS/TLS (How Websites Stay Secure)

def explain_https():
"""Explain how HTTPS works using what we learned"""
print("How HTTPS Works (Using Our Knowledge!)")
print("=" * 45)
print("When you visit https://bank.com:")
print()
print("Step 1: Certificate Exchange")
print("-" * 30)
print("Bank sends you their public key (in a certificate)")
print("Your browser verifies the certificate is real")
print("Now you trust the bank's public key")
print()
print("Step 2: Hybrid Encryption Setup")
print("-" * 35)
print("Your browser generates random AES key")
print("Browser encrypts AES key with bank's PUBLIC key")
print("Encrypted AES key sent to bank")
print("Bank decrypts AES key with their PRIVATE key")
print("Now both have the same AES key!")
print()
print("Step 3: Secure Communication")
print("-" * 32)
print("All messages encrypted with shared AES key")
print("Fast symmetric encryption for everything")
print("Login details, credit card info, etc. all protected")
print("Even if someone intercepts, they can't read it")
print()
print("This uses everything we learned:")
print(" • Public key cryptography (RSA)")
print(" • Symmetric encryption (AES)")
print(" • Hybrid encryption (combining both)")
print(" • Digital certificates (proving identity)")
print(" • Hashing (for integrity)")
explain_https()
Output:
How HTTPS Works (Using Our Knowledge!)
=============================================
When you visit https://bank.com:
Step 1: Certificate Exchange
------------------------------
Bank sends you their public key (in a certificate)
Your browser verifies the certificate is real
Now you trust the bank's public key
Step 2: Hybrid Encryption Setup
-----------------------------------
Your browser generates random AES key
Browser encrypts AES key with bank's PUBLIC key
Encrypted AES key sent to bank
Bank decrypts AES key with their PRIVATE key
Now both have the same AES key!
Step 3: Secure Communication
--------------------------------
All messages encrypted with shared AES key
Fast symmetric encryption for everything
Login details, credit card info, etc. all protected
Even if someone intercepts, they can't read it
This uses everything we learned:
• Public key cryptography (RSA)
• Symmetric encryption (AES)
• Hybrid encryption (combining both)
• Digital certificates (proving identity)
• Hashing (for integrity)
Secure Messaging App
class SecureMessagingApp:
"""Simple secure messaging system using our knowledge"""
def __init__(self, name):
self.name = name
self.private_key = None
self.public_key = None
self.contacts = {} # Store other people's public keys
def setup_keys(self):
"""Generate keys for this user"""
self.private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
)
self.public_key = self.private_key.public_key()
print(f"{self.name} generated secure keys!")
def add_contact(self, contact_name, contact_public_key):
"""Add someone's public key to contacts"""
self.contacts[contact_name] = contact_public_key
print(f"{self.name} added {contact_name} to contacts")
def send_message(self, recipient_name, message):
"""Send encrypted message to someone"""
if recipient_name not in self.contacts:
print(f"{recipient_name} not in contacts!")
return None
print(f"{self.name} → {recipient_name}: '{message}'")
# Use hybrid encryption
# Step 1: Generate AES key
aes_key = Fernet.generate_key()
# Step 2: Encrypt message with AES
fernet = Fernet(aes_key)
encrypted_message = fernet.encrypt(message.encode())
# Step 3: Encrypt AES key with recipient's public key
recipient_public_key = self.contacts[recipient_name]
encrypted_aes_key = recipient_public_key.encrypt(
aes_key,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
# Step 4: Create digital signature to prove it's from us
signature = self.private_key.sign(
message.encode(),
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
print("Message encrypted and signed!")
return {
'encrypted_message': encrypted_message,
'encrypted_key': encrypted_aes_key,
'signature': signature,
'sender': self.name
}
def receive_message(self, encrypted_data):
"""Receive and decrypt a message"""
sender_name = encrypted_data['sender']
if sender_name not in self.contacts:
print(f"Unknown sender: {sender_name}")
return None
print(f"{self.name} received message from {sender_name}")
# Step 1: Decrypt AES key with our private key
aes_key = self.private_key.decrypt(
encrypted_data['encrypted_key'],
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
# Step 2: Decrypt message with AES key
fernet = Fernet(aes_key)
decrypted_message = fernet.decrypt(encrypted_data['encrypted_message']).decode()
# Step 3: Verify signature to make sure it's really from sender
sender_public_key = self.contacts[sender_name]
try:
sender_public_key.verify(
encrypted_data['signature'],
decrypted_message.encode(),
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
signature_valid = True
except:
signature_valid = False
print(f"Message decrypted!")
print(f"Signature valid: {'YES' if signature_valid else 'NO'}")
print(f"Message: '{decrypted_message}'")
return decrypted_message if signature_valid else None
def secure_messaging_demo():
"""Demo of secure messaging app"""
print("Secure Messaging App Demo")
print("=" * 30)
# Create users
alice = SecureMessagingApp("Alice")
bob = SecureMessagingApp("Bob")
# Generate their keys
alice.setup_keys()
bob.setup_keys()
print()
# They exchange public keys (like adding contacts)
print("Step 1: Exchange public keys")
print("-" * 32)
alice.add_contact("Bob", bob.public_key)
bob.add_contact("Alice", alice.public_key)
print()
# Alice sends message to Bob
print("Step 2: Alice sends secure message to Bob")
print("-" * 45)
message1 = "Hey Bob! Want to meet at the coffee shop?"
encrypted_msg = alice.send_message("Bob", message1)
print()
# Bob receives and decrypts
print("Step 3: Bob receives and decrypts")
print("-" * 37)
decrypted1 = bob.receive_message(encrypted_msg)
print()
# Bob replies
print("Step 4: Bob replies to Alice")
print("-" * 30)
message2 = "Sure Alice! See you at 3 PM. I'll bring the documents."
encrypted_reply = bob.send_message("Alice", message2)
print()
# Alice receives reply
print("Step 5: Alice receives Bob's reply")
print("-" * 36)
decrypted2 = alice.receive_message(encrypted_reply)
print()
print("What our messaging app provides:")
print(" End-to-end encryption (only sender and receiver can read)")
print(" Authentication (proves who sent each message)")
print(" Integrity (detects if messages are tampered with)")
print(" No size limits (hybrid encryption handles any message length)")
print(" Forward secrecy (new keys for each conversation)")
secure_messaging_demo()
Output:
Secure Messaging App Demo
==============================
Alice generated secure keys!
Bob generated secure keys!
Step 1: Exchange public keys
--------------------------------
Alice added Bob to contacts
Bob added Alice to contacts
Step 2: Alice sends secure message to Bob
---------------------------------------------
Alice → Bob: 'Hey Bob! Want to meet at the coffee shop?'
Message encrypted and signed!
Step 3: Bob receives and decrypts
-------------------------------------
Bob received message from Alice
Message decrypted!
Signature valid: YES
Message: 'Hey Bob! Want to meet at the coffee shop?'
Step 4: Bob replies to Alice
------------------------------
Bob → Alice: 'Sure Alice! See you at 3 PM. I'll bring the documents.'
Message encrypted and signed!
Step 5: Alice receives Bob's reply
------------------------------------
Alice received message from Bob
Message decrypted!
Signature valid: YES
Message: 'Sure Alice! See you at 3 PM. I'll bring the documents.'
What our messaging app provides:
End-to-end encryption (only sender and receiver can read)
Authentication (proves who sent each message)
Integrity (detects if messages are tampered with)
No size limits (hybrid encryption handles any message length)
Forward secrecy (new keys for each conversation)
Security Best Practices
Now that you understand cryptography, let’s learn how to use it safely!
The Golden Rules of Cryptography
def security_best_practices():
"""Learn the important security rules"""
print("Cryptography Security Best Practices")
print("=" * 45)
practices = [
{
"rule": "1. Never implement crypto yourself for production",
"why": "Cryptography is extremely hard to get right. Use proven libraries.",
"good": "Use: cryptography library, bcrypt, etc.",
"bad": "Don't: Write your own AES implementation"
},
{
"rule": "2. Use strong, random keys",
"why": "Weak keys = weak security, no matter how good your algorithm.",
"good": "Use: os.urandom(), secrets module",
"bad": "Don't: Use predictable keys like '12345' or current time"
},
{
"rule": "3. Never reuse keys inappropriately",
"why": "Key reuse can leak information and weaken security.",
"good": "Use: New IV for each encryption, unique salts",
"bad": "Don't: Same IV/salt for multiple encryptions"
},
{
"rule": "4. Use authenticated encryption",
"why": "Encryption without authentication can be manipulated.",
"good": "Use: Fernet, AES-GCM, ChaCha20-Poly1305",
"bad": "Don't: Plain AES-CBC without HMAC"
},
{
"rule": "5. Keep private keys private!",
"why": "If private key is compromised, all security is lost.",
"good": "Use: Secure storage, hardware security modules",
"bad": "Don't: Store in plain text, commit to version control"
},
{
"rule": "6. Use proper key derivation for passwords",
"why": "Simple hashing is too fast - attackers can guess quickly.",
"good": "Use: PBKDF2, bcrypt, scrypt, Argon2",
"bad": "Don't: MD5, SHA-1, or plain SHA-256 for passwords"
},
{
"rule": "7. Validate and verify everything",
"why": "Trust but verify - check signatures and certificates.",
"good": "Use: Certificate validation, signature verification",
"bad": "Don't: Accept any certificate, skip signature checks"
},
{
"rule": "8. Use timing-safe comparisons",
"why": "Timing attacks can reveal secret information.",
"good": "Use: hmac.compare_digest() for secret comparisons",
"bad": "Don't: Use == for comparing hashes/MACs"
}
]
for i, practice in enumerate(practices, 1):
print(f"\n{practice['rule']}")
print("─" * len(practice['rule']))
print(f"Why: {practice['why']}")
print(f"{practice['good']}")
print(f"{practice['bad']}")
print("\n Remember:")
print(" • Security is only as strong as the weakest link")
print(" • When in doubt, ask security experts")
print(" • Stay updated - security is always evolving")
print(" • Test your security implementations thoroughly")
security_best_practices()
Output:
Cryptography Security Best Practices
=============================================
1. Never implement crypto yourself for production
──────────────────────────────────────────────────
Why: Cryptography is extremely hard to get right. Use proven libraries.
Use: cryptography library, bcrypt, etc.
Don't: Write your own AES implementation
2. Use strong, random keys
──────────────────────────
Why: Weak keys = weak security, no matter how good your algorithm.
Use: os.urandom(), secrets module
Don't: Use predictable keys like '12345' or current time
3. Never reuse keys inappropriately
───────────────────────────────────
Why: Key reuse can leak information and weaken security.
Use: New IV for each encryption, unique salts
Don't: Same IV/salt for multiple encryptions
4. Use authenticated encryption
───────────────────────────────
Why: Encryption without authentication can be manipulated.
Use: Fernet, AES-GCM, ChaCha20-Poly1305
Don't: Plain AES-CBC without HMAC
5. Keep private keys private!
─────────────────────────────
Why: If private key is compromised, all security is lost.
Use: Secure storage, hardware security modules
Don't: Store in plain text, commit to version control
6. Use proper key derivation for passwords
──────────────────────────────────────────
Why: Simple hashing is too fast - attackers can guess quickly.
Use: PBKDF2, bcrypt, scrypt, Argon2
Don't: MD5, SHA-1, or plain SHA-256 for passwords
7. Validate and verify everything
─────────────────────────────────
Why: Trust but verify - check signatures and certificates.
Use: Certificate validation, signature verification
Don't: Accept any certificate, skip signature checks
8. Use timing-safe comparisons
──────────────────────────────
Why: Timing attacks can reveal secret information.
Use: hmac.compare_digest() for secret comparisons
Don't: Use == for comparing hashes/MACs
Remember:
• Security is only as strong as the weakest link
• When in doubt, ask security experts
• Stay updated - security is always evolving
• Test your security implementations thoroughly
Quick Reference & Cheat Sheet
Here’s a handy reference for everything we learned!
def crypto_cheat_sheet():
"""Quick reference for cryptography in Python"""
print("Python Cryptography Cheat Sheet")
print("=" * 40)
sections = {
"HASHING": {
"Simple hash": "hashlib.sha256(data.encode()).hexdigest()",
"File hash": "hash_obj.update(file_chunk) # in loop",
"Password (secure)": "hashlib.pbkdf2_hmac('sha256', password, salt, iterations)",
"Message auth": "hmac.new(key, message, hashlib.sha256).hexdigest()"
},
"SYMMETRIC ENCRYPTION": {
"Simple (Fernet)": "Fernet(key).encrypt(message.encode())",
"Generate key": "Fernet.generate_key()",
"Decrypt": "Fernet(key).decrypt(encrypted_data).decode()"
},
"ASYMMETRIC ENCRYPTION": {
"Generate keys": "rsa.generate_private_key(public_exponent=65537, key_size=2048)",
"Get public key": "private_key.public_key()",
"Encrypt": "public_key.encrypt(data, padding.OAEP(...))",
"Decrypt": "private_key.decrypt(encrypted_data, padding.OAEP(...))"
},
"DIGITAL SIGNATURES": {
"Sign": "private_key.sign(data, padding.PSS(...), hashes.SHA256())",
"Verify": "public_key.verify(signature, data, padding.PSS(...), hashes.SHA256())"
},
"HYBRID ENCRYPTION": {
"Process": "1. Generate AES key\n 2. Encrypt data with AES\n 3. Encrypt AES key with RSA\n 4. Send both encrypted data and key"
},
"COMMON IMPORTS": {
"Basic": "import hashlib, hmac, secrets, os",
"Fernet": "from cryptography.fernet import Fernet",
"RSA": "from cryptography.hazmat.primitives.asymmetric import rsa, padding",
"Hashes": "from cryptography.hazmat.primitives import hashes"
}
}
for section_name, items in sections.items():
print(f"\n{section_name}")
print("─" * (len(section_name) - 2))
for name, code in items.items():
if '\n' in code: # Multi-line code
print(f"{name}:")
for line in code.split('\n'):
print(f" {line}")
else:
print(f"{name:15}: {code}")
print(f"\n WHEN TO USE WHAT")
print("─" * 18)
print("Hashing : Passwords, file integrity, digital fingerprints")
print("Symmetric : Fast encryption, large files, known parties")
print("Asymmetric : Key exchange, unknown parties, digital signatures")
print("Hybrid : Best of both worlds, real-world applications")
print("HMAC : Message authentication, API signatures")
print(f"\n SECURITY LEVELS")
print("─" * 16)
print("Basic : MD5, SHA-1 (avoid for security)")
print("Good : SHA-256, AES-128, RSA-2048")
print("Better : SHA-3, AES-256, RSA-3072+")
print("Best : Argon2, ChaCha20-Poly1305, Ed25519")
crypto_cheat_sheet()
Output:
Python Cryptography Cheat Sheet
========================================
HASHING
──────────
Simple hash : hashlib.sha256(data.encode()).hexdigest()
File hash : hash_obj.update(file_chunk) # in loop
Password (secure): hashlib.pbkdf2_hmac('sha256', password, salt, iterations)
Message auth : hmac.new(key, message, hashlib.sha256).hexdigest()
SYMMETRIC ENCRYPTION
──────────────────────
Simple (Fernet): Fernet(key).encrypt(message.encode())
Generate key : Fernet.generate_key()
Decrypt : Fernet(key).decrypt(encrypted_data).decode()
ASYMMETRIC ENCRYPTION
───────────────────────
Generate keys : rsa.generate_private_key(public_exponent=65537, key_size=2048)
Get public key : private_key.public_key()
Encrypt : public_key.encrypt(data, padding.OAEP(...))
Decrypt : private_key.decrypt(encrypted_data, padding.OAEP(...))
DIGITAL SIGNATURES
─────────────────────
Sign : private_key.sign(data, padding.PSS(...), hashes.SHA256())
Verify : public_key.verify(signature, data, padding.PSS(...), hashes.SHA256())
HYBRID ENCRYPTION
───────────────────
Process:
1. Generate AES key
2. Encrypt data with AES
3. Encrypt AES key with RSA
4. Send both encrypted data and key
COMMON IMPORTS
───────────────
Basic : import hashlib, hmac, secrets, os
Fernet : from cryptography.fernet import Fernet
RSA : from cryptography.hazmat.primitives.asymmetric import rsa, padding
Hashes : from cryptography.hazmat.primitives import hashes
WHEN TO USE WHAT
──────────────────
Hashing : Passwords, file integrity, digital fingerprints
Symmetric : Fast encryption, large files, known parties
Asymmetric : Key exchange, unknown parties, digital signatures
Hybrid : Best of both worlds, real-world applications
HMAC : Message authentication, API signatures
SECURITY LEVELS
────────────────
Basic : MD5, SHA-1 (avoid for security)
Good : SHA-256, AES-128, RSA-2048
Better : SHA-3, AES-256, RSA-3072+
Best : Argon2, ChaCha20-Poly1305, Ed25519
Conclusion: Your Crypto Journey
Congratulations! You’ve just completed a comprehensive journey through hashing and cryptography in Python. Let’s recap what you’ve mastered:
What You Now Know:
Hashing Mastery:
- Created digital fingerprints for any data
- Secured passwords with salt and PBKDF2
- Verified file integrity and detected tampering
- Built message authentication with HMAC
Encryption Expertise:
- Scrambled messages with symmetric encryption (Fernet/AES)
- Exchanged secrets securely with asymmetric encryption (RSA)
- Combined both with hybrid cryptography for maximum power
- Encrypted files and protected sensitive data
Authentication Skills:
- Created digital signatures to prove identity
- Verified message authenticity and integrity
- Built trust systems without meeting beforehand
Real-World Applications:
- Understanding how HTTPS protects websites
- Building secure messaging systems
- Following security best practices
- Knowing when and how to use each technique
Your Next Steps:
- Practice: Try modifying the code examples to encrypt your own files and messages
- Explore: Look into advanced topics like elliptic curve cryptography (ECC)
- Build: Create your own secure applications using these principles
- Learn: Study how real systems like Signal, WhatsApp, and browsers use cryptography
- Stay Updated: Cryptography evolves – follow security news and updates
Final Words:
You now have the knowledge to protect data, communicate securely, and understand how the secure digital world works around you. Every time you see a padlock icon in your browser, send a WhatsApp message, or log into a website, you’ll understand the cryptographic magic happening behind the scenes.
Remember: With great cryptographic power comes great responsibility! Always use these techniques ethically and follow security best practices.
Quick Practice Challenge:
Try creating a simple program that:
- Generates RSA keys for two users
- Lets them exchange public keys
- Allows secure message exchange with hybrid encryption
- Verifies each message with digital signatures
You have all the tools now – go build something amazing and secure!
This guide covered practical cryptography in Python from basic concepts to real-world applications. Remember to always use proven libraries and follow security best practices in production systems.
Test Your Cryptography Knowledge
Challenge yourself with 15 questions covering hashing, encryption, and security concepts
External resources
Official Documentation
Python hashlib Documentation: https://docs.python.org/3/library/hashlib.html
Official Python documentation for hash functions including SHA, MD5, BLAKE2, and more. Essential reference for implementing hashing in Python.
Cryptography Library Documentation: https://cryptography.io/en/latest/
Comprehensive documentation for Python’s cryptography library covering Fernet, RSA, AES, and cryptographic primitives with practical examples.
PyCryptodome Documentation: https://pycryptodome.readthedocs.io/
Self-contained Python cryptographic library with extensive algorithms and protocols. Great alternative to the cryptography library with more implementation options.
Python secrets Module: https://docs.python.org/3/library/secrets.html
Generate cryptographically strong random numbers suitable for managing secrets like passwords, authentication tokens, and security keys.
Online Courses & Interactive Learning
Cryptography I – Stanford University (Coursera) : https://www.coursera.org/learn/crypto
Stanford’s comprehensive cryptography course covering mathematical foundations, encryption schemes, message integrity, and cryptographic protocols. Includes video lectures, assignments, and quizzes.
Applied Cryptography – Udacity: https://www.udacity.com/course/applied-cryptography–cs387
Practical cryptography course focusing on real-world applications, security protocols, and cryptographic implementations used in modern systems.
Cryptopals Crypto Challenges: https://cryptopals.com/
Hands-on cryptography challenges teaching you to break crypto implementations. Learn by doing with progressively difficult exercises covering real-world vulnerabilities.
Khan Academy – Cryptography: https://www.khanacademy.org/computing/computer-science/cryptography
Free video series explaining cryptography fundamentals with visual demonstrations. Perfect for beginners starting their cryptography journey.
Cryptographic Tools & Libraries
OpenSSL: https://www.openssl.org/
Industry-standard toolkit for SSL/TLS protocols and general-purpose cryptography library. Essential for production systems and understanding web security.
Python bcrypt Library: GitHub:https://github.com/pyca/bcrypt
Documentation:https://github.com/pyca/bcrypt/#readme
Modern password hashing library implementing bcrypt algorithm. Industry best practice for secure password storage with built-in salt generation.
CyberChef: https://gchq.github.io/CyberChef/
Browser-based tool for encryption, encoding, compression, and data analysis. Great for learning, experimentation, and quick cryptographic operations.
libsodium (PyNaCl for Python):
Link:https://github.com/jedisct1/libsodium
Documentation:https://pynacl.readthedocs.io/
Modern, easy-to-use software library for encryption, decryption, signatures, and password hashing. Focuses on high security and ease of use.
Leave a Reply