Blog/Verify a media file with a SHA-256 checksum in Python
OverviewAll posts
Tutorial

Verify a media file with a SHA-256 checksum in Python

Check downloaded media bytes with Python SHA-256, preserve a trusted reference, and distinguish file integrity from S3 ETags and media quality.

VTornado API team
Covered in this article
Compare file bytes against a trusted reference
Run a bounded-memory Python example
Handle mismatches before downstream processing
7 min reading time
Published October 20, 2026
VProduct guides by Velys Software

To verify that a downloaded media file matches an expected file, calculate a SHA-256 digest over its bytes and compare it with a trusted digest for that exact artifact. Record the byte count too. A matching filename, HTTP success response or playable opening frame cannot establish that the complete transfer preserved the file.

The important word is expected. Computing a checksum for the first time after downloading creates a fingerprint of what you received. Without an independent reference, it does not prove that those bytes are the intended source. This guide shows a local Python check and explains where to place it in a media ingestion workflow.

Choose the artifact you want to preserve

A video source can produce several legitimate outputs: a full video, an audio-only file, a clip or a subtitle file. Each artifact needs its own identity and checksum. Do not compare the hash of an extracted audio file with the hash of its parent video. Re-encoding, remuxing or changing embedded metadata can also change bytes even when playback looks similar.

For Tornado, first obtain the delivery information through the job status reference. The job identifier connects the delivery to your request. The checksum record below belongs to your application; it does not assume that Tornado returns a SHA-256 field.

Keep the fingerprint alongside a stable artifact reference, its output role, the job ID and the time it was checked. Store temporary access links separately. If your workflow resumes a transfer, complete the interrupted-download checks before comparing the final file.

Establish a trustworthy reference

There are two useful situations:

  • You control the original artifact. Calculate its digest after the producing process has finished, retain that record, then compare the copied or downloaded artifact against it.
  • You only have a newly delivered artifact. Validate that it is the intended media, then record its digest as a baseline for subsequent copies. Label it as a baseline, not independent proof of the first download.

Protect the reference from accidental replacement. If the same failing transfer overwrites both the file and its expected checksum, the comparison loses its purpose. For a pipeline with mutable object keys, retain the relevant version identifier where the storage service supports it, or use a new immutable application artifact ID for each revision.

A checksum comparison is also not an authenticity signature. If an attacker can replace both a file and the unprotected checksum you trust, a matching result does not establish who produced the content.

Hash a local file in bounded chunks

Python's hashlib documentation defines sha256, incremental update and hexadecimal output. The helper below reads one MiB at a time, so it does not load an entire video into memory. It returns the number of bytes actually read together with a lowercase SHA-256 string.

Run it on a completed, closed file that no other process is modifying. The expected digest in the demonstration is a known value for the three bytes abc; those bytes are a test fixture, not a video.

import hashlib
import re
from pathlib import Path
from tempfile import TemporaryDirectory


def fingerprint(path):
    digest = hashlib.sha256()
    size = 0
    with open(path, "rb") as source:
        while chunk := source.read(1024 * 1024):
            digest.update(chunk)
            size += len(chunk)
    return size, digest.hexdigest()


def verify_file(path, expected_size, expected_sha256):
    if type(expected_size) is not int or expected_size < 0:
        raise ValueError("Expected size must be a nonnegative integer")
    if not isinstance(expected_sha256, str) or not re.fullmatch(
        r"[0-9a-fA-F]{64}", expected_sha256
    ):
        raise ValueError("Expected SHA-256 must contain 64 hex characters")
    size, actual = fingerprint(path)
    return size == expected_size and actual == expected_sha256.lower()


with TemporaryDirectory() as directory:
    path = Path(directory) / "fixture.bin"
    expected = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
    path.write_bytes(b"abc")
    assert verify_file(path, 3, expected)
    assert verify_file(path, 3, expected.upper())
    assert not verify_file(path, 4, expected)
    path.write_bytes(b"abd")  # Same length, different bytes.
    assert not verify_file(path, 3, expected)
    path.write_bytes(b"ab")   # Truncated copy.
    assert not verify_file(path, 3, expected)

Replace the fixture path and expected values with your own completed file and trusted record when integrating the helper. A False result means the supplied size or checksum differs. File access failures raise exceptions; catch and report them separately from a checksum mismatch.

The five assertions above were executed locally on synthetic bytes. Additional local checks covered empty files, a file larger than one chunk, malformed digests, invalid sizes and missing files. These checks do not validate a live media transfer, storage provider or Tornado job.

Do not substitute an S3 ETag for SHA-256

An ETag is not a universal checksum format. Amazon's S3 Object reference explicitly distinguishes cases where an ETag is an MD5 digest from cases where it is not, including multipart uploads. An ETag is therefore not the SHA-256 value expected by this helper.

If you use a provider checksum, check its algorithm, encoding and scope before comparing it. A hexadecimal full-file SHA-256 value is different from a Base64 representation or a checksum composed from multiple upload parts. Follow the provider's documented verification mechanism rather than stripping punctuation from a header and hoping it matches.

For bucket access and object existence, use the S3 delivery checklist. That operational check and a byte comparison answer different questions: whether the consumer can reach an object, and whether the obtained artifact matches the selected reference.

Keep a mismatch out of downstream processing

Download into a temporary artifact that consumers cannot pick up as finished. Once the transfer has closed, compare against the trusted reference when one exists. Only then promote the file into your application's ready state. Keep that promotion coordinated with your worker's claim mechanism so two workers cannot publish competing revisions.

If the comparison fails, retain the attempt identifier and diagnostic outcome, but do not silently replace the expected digest with the new result. Check whether the reference belongs to another output, whether the object changed, and whether the transfer was incomplete. Retry the transfer of the existing artifact when appropriate; a local checksum mismatch alone does not justify creating a new source-processing job.

Avoid logging signed delivery URLs while investigating. Log an application artifact ID, job ID, byte counts and a controlled mismatch code. The safe logging guide explains how to keep temporary credentials out of diagnostics.

Validate media separately from its bytes

A correct checksum can preserve a corrupt or unwanted file perfectly. After byte verification, inspect the media properties and representative playback that your consumer needs: duration, audio presence, resolution and the intended excerpt. The delivered-resolution guide covers one of those checks.

The helper also does not prevent a file from changing after verification. Use a private staging directory, immutable artifact revisions and controlled publication in a real application. Do not treat this small example as a filesystem locking or hostile-input security system.

Start with one complete media workflow, retain the delivered artifact's identity, and add a checksum baseline at the handoff your application owns. Then test a truncated copy and a same-length altered copy before allowing that handoff to feed a larger batch.