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
253: The hashlib Module for Hashing
I remember a junior dev I worked with a few years back who tried to verify a 4GB database dump by simply comparing the file size. It seemed logical—if the sizes matched, the file must be intact. The problem was that the transfer had failed silently halfway through, but the file ended up being the exact same size as the original due to a weird fluke in the network buffer. He spent six hours debugging a "corrupt" database that he was convinced was perfectly fine. That's the moment I sat him down and introduced him to checksums and the hashlib module.
Generating a Unique Digital Fingerprint
At its core, a cryptographic hash function takes an input—be it a short string or a massive movie file—and turns it into a fixed-length string of characters. If you change even a single bit in that input, the resulting hash changes completely. This is what we call the "avalanche effect."
In Python, the hashlib module is your go-to for this. One thing that trips people up immediately is that hashlib doesn't take strings directly; it requires bytes. You'll need to encode your strings using .encode() before passing them in. Here is the basic workflow using SHA-256, which is generally the industry standard for non-password hashing:
import hashlib
message = "Verify this specific string"
# We encode the string to bytes, then pass it to sha256
hash_object = hashlib.sha256(message.encode())
hex_dig = hash_object.hexdigest()
print(f"The SHA-256 hash is: {hex_dig}")
I usually recommend hexdigest() over digest() because it returns a readable string instead of a raw bytes object, making it much easier to store in a database or print in a log file.
Hashing Large Files Without Crashing Your RAM
If you try to read a 10GB file into memory just to hash it, your OS is going to kill your Python process pretty quickly. To handle large files, you shouldn't pass the whole file into the constructor. Instead, you create the hash object first and then use the .update() method to feed it data in small chunks.
This is how professional file-transfer tools actually work. You stream the file from the disk, update the hash state, and move on to the next block. It keeps your memory footprint tiny regardless of the file size:
import hashlib
def get_file_sha256(filename):
sha256_hash = hashlib.sha256()
with open(filename, "rb") as f:
# Read the file in 4KB chunks
for byte_block in iter(lambda: f.read(4096), b""):
sha256_hash.update(byte_block)
return sha256_hash.hexdigest()
# Now you can safely hash a file of any size
print(get_file_sha256("massive_backup.sql"))
Choosing the Right Algorithm
You'll see options like md5 and sha1 in the module. In the old days, these were the gold standard. Today? Avoid them for anything security-related. MD5 is fast, but it's "broken," meaning it's computationally easy to create two different files that produce the same MD5 hash (a collision). If you're just checking for accidental corruption in a non-critical file, MD5 is fine. If you're dealing with security, signatures, or sensitive data, stick to the SHA-2 family (like sha256) or SHA-3.
Just a quick heads-up: if you are planning to store user passwords, hashlib alone isn't enough. You'll need to look into "salting" and algorithms designed for passwords like bcrypt or argon2, which are intentionally slower to prevent brute-force attacks. hashlib is built for speed; password hashing is built for resistance.
📋 Practical Task
Build a File Integrity Validator
Imagine you are distributing a software patch. To ensure users don't download a corrupted version, you provide them with a "known good" SHA-256 hash. Your task is to write a script that validates a local file against a provided hash string.
Requirements:
- Create a function
verify_file(file_path, expected_hash). - The function must read the file in chunks (to handle large files) and calculate its SHA-256 hash.
- The function should return
Trueif the calculated hash matches theexpected_hash(case-insensitive), andFalseotherwise. - Write a small block of code to test this: create a dummy text file, calculate its hash manually, and then use your
verify_filefunction to confirm it matches.
There are no comments for now.