Practical Python Security: Don’t Expose Passwords by Misusing Hashlib

Python tutorial - IT technology blog
Python tutorial - IT technology blog

Why are Hashing and Random String Generation Important?

When I first started web development, I made a silly mistake: storing user passwords as plain text in the database, a common mistake when not following Python Design Patterns. At the time, I reassured myself, “no one will be able to hack it.” The result was that after just a small SQL Injection vulnerability, all customer data was exposed. That was a costly lesson that forced me to take learning how to use hashlib and secrets seriously.

Many people still confuse Hashing and Encryption. Encryption is a two-way street; you can decrypt it if you have the key. In contrast, Hashing is a one-way process. Once hashed, you cannot reverse it to its original value. This is the final layer of defense. Even if a hacker gets the database, all they see are meaningless strings of characters.

Additionally, generating password reset tokens requires caution. If you use the standard random module, hackers can predict the next value based on system time. Since handling time in Python can lead to predictability if not careful, the secrets module was born to solve this. It ensures randomness that meets security standards (cryptographically secure).

Environment Setup

Both hashlib and secrets are standard Python libraries. You don’t need to pip install anything. Just have Python 3.6 or later installed, and you’re ready to go.

import hashlib
import secrets
import hmac

print("Environment is ready!")

For production projects in 2024, I recommend using Python 3.9+. This version offers better hashing performance optimization for high-load systems, similar to the focus on performance in Python 3.13.

Real-world Implementation and Detailed Configuration

1. Password Hashing with hashlib (Avoid MD5 at all costs)

Many old tutorials still teach using MD5 or SHA-1. My advice: Stop immediately! A modern GPU can attempt billions of MD5 hashes per second. It only takes a hacker a few minutes to crack your passwords using Rainbow Tables.

The secure choice today is SHA-256 combined with a Salt and an iteration mechanism. I usually use pbkdf2_hmac to slow down the hashing process. This makes brute-force attacks extremely expensive and impossible.

def hash_password(password: str):
    # Generate a random 16-byte salt
    salt = secrets.token_bytes(16)
    # Use PBKDF2 with 100,000 iterations
    pw_hash = hashlib.pbkdf2_hmac(
        'sha256', 
        password.encode('utf-8'), 
        salt, 
        100000
    )
    return salt + pw_hash

# Testing
raw_pw = "StrongPassword2024!"
stored_value = hash_password(raw_pw)
print(f"Stored in DB: {stored_value.hex()}")

When I need to quickly compare hash results without writing code, I often use Hash Generator. This tool runs entirely in the browser (client-side). Data is never sent to the server, so it’s very safe for testing sensitive strings.

2. Generating Tokens and Temporary Passwords with secrets

When implementing a “Forgot Password” feature, you need a token to send via email. Don’t use random.randint() because it’s highly predictable. Use secrets to generate unpredictable strings.

# Generate a secure URL token
reset_token = secrets.token_urlsafe(32)
print(f"Email token: {reset_token}")

# Generate a 16-character temporary password
temp_pass = secrets.token_hex(8)
print(f"Temporary password: {temp_pass}")

If you are an admin and need to quickly generate a list of strong passwords for employees, try Password Generator. It allows customization of length, special characters, and visually measures password strength.

3. Data Authentication with HMAC

HMAC helps you ensure that data is not tampered with during transmission between systems (such as Discord Webhooks). It’s like placing a security seal on your package.

KEY = b'important-secret'
msg = b"Action: Transfer; Amount: 1000"

# Create signature
sig = hmac.new(KEY, msg, hashlib.sha256).hexdigest()

# Check integrity
def verify(message, received_sig):
    expected = hmac.new(KEY, message, hashlib.sha256).hexdigest()
    # Prevent Timing Attacks using compare_digest
    return hmac.compare_digest(expected, received_sig)

Testing and Monitoring Experience

A basic but dangerous mistake is using the == operator to compare hashes. Hackers can measure the server’s response time to guess each character (Timing Attack). Always use hmac.compare_digest() to ensure the processing time remains constant.

Important Notes:

  • Never log unhashed passwords or Salt strings into the system.
  • If you see a sudden spike in password reset requests (e.g., >100 times/minute), trigger an attack alert.
  • Increase the number of PBKDF2 iterations every 2 years to keep up with hardware speeds.

While debugging APIs, if I encounter strange Base64 strings, I usually use Base64 Decoder to quickly check the content. This tool processes on the client side, so I’m confident that API keys or tokens won’t be leaked.

Security is not a “set it and forget it” task. It’s a habit of writing careful code every day, much like the process of mastering automated Python API testing. Good luck building secure Python applications!

Share: