Skip to Content
Course content

253: The hashlib Module for Hashing

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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 True if the calculated hash matches the expected_hash (case-insensitive), and False otherwise.
  • Write a small block of code to test this: create a dummy text file, calculate its hash manually, and then use your verify_file function to confirm it matches.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.