Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
261: Password Hashing with bcrypt
Imagine you're running a high-security vault. Instead of keeping a list of every customer's actual key, which would be a disaster if a thief stole your list, you use a specialized meat grinder. When a customer creates a key, you run it through the grinder. You don't keep the key; you only keep the resulting pile of ground-up metal. Now, when that customer comes back, they give you their key again, you run it through the same grinder, and if the new pile of metal looks exactly like the old pile, you know they have the right key.
Crucially, no matter how hard a thief tries, they can't "un-grind" that metal to figure out what the original key looked like. That's the core of password hashing.
In Python, we use bcrypt for this. It doesn't just "grind" the password; it adds a "pinch of salt" (random data) so that two people with the same password don't end up with the same hash. It also lets us decide how slow the grinder is, which makes it incredibly expensive for hackers to try billions of guesses per second.
Turning Passwords into Unreadable Hashes
First, you'll need the library: pip install bcrypt. One thing that trips people up immediately is that bcrypt doesn't want strings; it wants bytes. You have to encode your password before passing it in.
import bcrypt
# This is what the user provides during sign-up
password = "SuperSecretPassword123".encode('utf-8')
# gensalt() handles the randomness for us automatically
# It also embeds the 'cost factor' (how slow the hash is)
salt = bcrypt.gensalt()
# Now we hash it
hashed = bcrypt.hashpw(password, salt)
print(hashed)
# You'll see a long string of bytes starting with $2b$
I want you to notice that we don't actually need to store the salt in a separate database column. bcrypt is clever—it bundles the salt and the cost factor directly into the resulting hash string. When it's time to verify the password, the library just reads the salt back out of the hash itself.
The Verification Process
You'll never "decrypt" a bcrypt hash because that's mathematically impossible. Instead, you take the password the user just typed in and check if it produces the same hash as the one you have stored.
# Let's assume 'hashed' is what we pulled from our database
user_input = "SuperSecretPassword123".encode('utf-8')
if bcrypt.checkpw(user_input, hashed):
print("Login successful!")
else:
print("Wrong password, buddy.")
Slowing Down the Attacker
The "cost factor" is where bcrypt really beats older methods like SHA-256. If you look at bcrypt.gensalt(rounds=12), that rounds parameter determines how many iterations the hashing algorithm goes through.
Every time you increase that number by one, you double the time it takes to hash the password. To a human, 0.3 seconds versus 0.6 seconds is invisible. But to a hacker trying to crack a million passwords a second, that delay is a brick wall. As computers get faster over the years, you can just bump up the rounds to keep your users safe without changing your entire codebase.
📋 Practical Task
Build a Secure User Credential Store
Write a small Python program that simulates a user registration and login system. Your code should implement the following:
- A dictionary called
user_dbwhere the keys are usernames and the values are the hashed passwords. - A function
register_user(username, password)that encodes the password, hashes it usingbcrypt, and saves it to the dictionary. - A function
login_user(username, password)that retrieves the hash from the dictionary and usesbcrypt.checkpwto verify the password. It should returnTrueif the login is successful andFalseotherwise.
Test your system by registering a user, attempting to log in with the wrong password (expect False), and then logging in with the correct password (expect True).
There are no comments for now.