Hashing, salting, and peppering
Hashing is a one-way transformation used to securely store sensitive values such as passwords. In Python, secure hashing is typically done using algorithms like bcrypt or hashlib, but hashing alone is not enough. To defend against rainbow tables (precomputed tables that map plaintext passwords to their hash values to enable fast reverse lookups of hashes), a unique salt is added to each password before hashing. The salt is stored alongside the hash in the database, ensuring that identical passwords produce different hashes while still allowing verification. A pepper is an additional secret value added to the password during hashing, but unlike the salt, the pepper is not stored in the database. Instead, it is kept in a secure location such as an environment variable, configuration file, or key vault. The database, therefore, stores only the hash and the salt, while the pepper remains hidden from attackers even if the database is compromised.
Hashed passwords cannot be decrypted, because hashing is a one‑way operation that permanently transforms the original password into a fixed-length digest with no mathematical method to reverse it. Instead of decrypting, password systems verify a login attempt by hashing the user’s input again using the same salt and pepper, then comparing the result to the stored hash. If the two hashes match, the password is correct. This design ensures that even if an attacker gains access to the database, they cannot recover the original passwords, because only the hash and salt are stored in the database, while the pepper remains securely stored in an environment variable or key vault.
import os, hashlib, hmac, base64
from secrets import token_bytes
salt = token_bytes(16) # generating a random salt for each password
pepper = os.environ.get("PEPPER_SECRET", "default_pepper").encode() # a secret pepper stored securely (e.g., an environment variable)
# Hashing a password with SHA-256 + salt + pepper
password = "my_secure_password".encode()
combined = salt + password + pepper
hash_value = hashlib.sha256(combined).hexdigest()
print(hash_value)
# Saving hash and salt for later (simulated storage example)
stored_salt = base64.b64encode(salt).decode() # encoding salt for storage (e.g., database, file)
stored_hash = hash_value
#### Later ####
def verify_password(stored_hash, salt, password):
combined = salt + password.encode() + pepper
new_hash = hashlib.sha256(combined).hexdigest()
return hmac.compare_digest(stored_hash, new_hash)
loaded_salt = base64.b64decode(stored_salt) # loading and decoding stored salt
print(verify_password(stored_hash, loaded_salt, "my_secure_password"))