TL;DR
Standard password hashing (like bcrypt or Argon2) is good, but adding a unique salt *per user* and stretching the hash multiple times makes cracking passwords much harder. This guide shows how to do it.
1. Understand Password Hashing Basics
Password hashing turns your plain text password into an unreadable string of characters. It’s a one-way process – you can’t get the original password back from the hash. Crucially, it should be slow to compute.
- Hashing Algorithms: Examples include SHA-256, bcrypt, Argon2. bcrypt and Argon2 are generally preferred because they automatically handle salting and stretching.
- Salts: Random data added to the password *before* hashing. This prevents attackers from using pre-computed tables of common passwords (rainbow tables).
- Stretching: Repeating the hashing process many times. This makes cracking slower, as it requires more computational power.
2. Why Custom Hashing?
While bcrypt and Argon2 are excellent, a custom approach gives you finer control over security parameters and can potentially offer increased resistance against future attacks if implemented carefully.
3. Implementing a Custom Hashing Procedure
- Generate a Unique Salt per User: Each user needs their own random salt. Store this salt securely alongside the hashed password (e.g., in your database).
- Choose a Strong Hashing Algorithm: SHA-256 is a good starting point, but consider newer algorithms if available in your programming language/framework.
- Multiple Stretching Rounds: Repeat the hashing process several times (e.g., 10,000 or more). The higher the number of rounds, the slower and more secure it becomes, but also increases processing time.
- Combine Salt and Hash for Storage: Store both the salt *and* the final hash in your database. Never store passwords in plain text!
- Verification Process: When a user logs in, retrieve their salt from the database. Re-hash the entered password using that same salt and compare it to the stored hash.
import os
salt = os.urandom(16) # Generate 16 bytes of random data
print(salt)
import hashlib
def hash_password(password, salt, rounds=10000):
hashed = password.encode('utf-8') # Encode to bytes
for _ in range(rounds):
hashed = hashlib.sha256(salt + hashed).digest()
return hashed
def verify_password(entered_password, stored_salt, stored_hash):
hashed = hash_password(entered_password, stored_salt)
return hashed == stored_hash
4. Important Considerations
- Salt Length: Use a salt length of at least 16 bytes (128 bits) for good security.
- Stretching Rounds: Experiment to find a balance between security and performance. Start with 10,000 rounds and increase if your server can handle it.
- Secure Storage: Protect the salts stored in your database. If an attacker gains access to the salts, they can crack passwords more easily.
- Regular Updates: Stay informed about new hashing algorithms and best practices in cyber security. Consider updating your hashing procedure periodically.
- Avoid Re-inventing the Wheel: bcrypt and Argon2 are well-tested and optimized. Only use a custom approach if you have specific requirements or expertise.