pypa/pip · error · HashMismatch

THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS

Error message

THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS FILE. If you have updated the package versions, please update the hashes. Otherwise, examine the package contents carefully; someone may have tampered with them.

What it means

HashMismatch (the headline 'THESE PACKAGES DO NOT MATCH THE HASHES...') raised by _raise() when none of the computed digests of the downloaded archive match any allowed digest in the requirements file. It signals either a legitimate version drift (hashes not refreshed after a version bump) or, worst case, a tampered/MITM'd artifact.

Source

Thrown at src/pip/_internal/utils/hashes.py:93

        """
        gots = {}
        for hash_name in self._allowed.keys():
            try:
                gots[hash_name] = hashlib.new(hash_name)
            except (ValueError, TypeError):
                raise InstallationError(f"Unknown hash name: {hash_name}")

        for chunk in chunks:
            for hash in gots.values():
                hash.update(chunk)

        for hash_name, got in gots.items():
            if got.hexdigest() in self._allowed[hash_name]:
                return
        self._raise(gots)

    def _raise(self, gots: dict[str, _Hash]) -> NoReturn:
        raise HashMismatch(self._allowed, gots)

    def check_against_file(self, file: BinaryIO) -> None:
        """Check good hashes against a file-like object

        Raise HashMismatch if none match.

        """
        return self.check_against_chunks(read_chunks(file))

    def check_against_path(self, path: str) -> None:
        with open(path, "rb") as file:
            return self.check_against_file(file)

    def has_one_of(self, hashes: Mapping[str, str]) -> bool:
        """Return whether any of the given hashes are allowed."""
        for hash_name, hex_digest in hashes.items():
            if self.is_hash_allowed(hash_name, hex_digest):
                return True

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. If you intentionally changed versions, regenerate hashes with `pip hash <file>` or pip-compile and update the requirements file.
  2. Verify the downloaded archive against the publisher's official checksum before trusting new hashes.
  3. Clear pip's cache (pip cache purge) and the mirror cache to rule out a stale/corrupt cached file.
  4. If you did not change anything, treat as a possible tamper: do NOT just update the hash — investigate the source.

Example fix

# before
pkg==1.0 --hash=sha256:OLDHASH...
# (you bumped to 1.1 but left the 1.0 hash)

# after - regenerate for the actual archive
pip download pkg==1.1 --no-deps -d /tmp/p
pip hash /tmp/p/pkg-1.1*.whl   # paste output back into requirements.txt
Defensive patterns

Strategy: validation

Validate before calling

import hashlib

def verify_archive_hash(path, expected_alg, expected_hex):
    h = hashlib.new(expected_alg)
    with open(path, 'rb') as f:
        for chunk in iter(lambda: f.read(1 << 16), b''):
            h.update(chunk)
    if h.hexdigest() != expected_hex:
        raise ValueError(f"{expected_alg} mismatch for {path}")
# run before pip if you pre-download archives; never auto-update on mismatch without trust review

Try / catch

try:
    pip_install('-r', 'reqs.txt')
except HashMismatch as e:
    # only safe to auto-refresh if you intentionally changed versions
    if versions_intentionally_bumped('reqs.txt'):
        regenerate_hashes('reqs.txt')
        pip_install('-r', 'reqs.txt')
    else:
        raise  # possible tamper — do NOT paper over it

Prevention

When it happens

Trigger: check_against_chunks computes gots for each allowed algorithm and none of got.hexdigest() values appear in self._allowed[hash_name]. Happens after download when --require-hashes is on or any requirement carries --hash and the archive bytes don't match.

Common situations: Bumped a version in requirements.txt but forgot to re-run pip-compile/hash; a mirror serving a cached-but-different file; a corporate proxy re-packaging wheels; an actual supply-chain attack; cross-posting a file that was re-uploaded in place.

Related errors


AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04). Data as JSON: /data/errors/563e730230e25926.json. Report an issue: GitHub.