pypa/pip · critical · HashMismatch

THESE PACKAGES DO NOT MATCH THE HASHES FROM THE…

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

Raised as HashMismatch by Hashes._raise (hashes.py:93) when none of the computed digests of a downloaded archive match any of the expected digests listed in the requirements file. This is pip's tamper-detection mechanism for --require-hashes mode: if even one file's hash differs from what was pinned, pip aborts to prevent installing potentially compromised or altered packages. The exception carries both the expected (allowed) and computed (gotten) hashes for diagnostics.

Solutions

  1. If you intentionally changed package versions, regenerate all hashes: delete the hashes, run `pip install --require-hashes -r requirements.txt`, and pip will print the correct hashes in the HashMissing error to add.
  2. Use `pip hash <downloaded-file>` to compute the correct sha256 for the exact file being installed.
  3. Ensure your index URL / mirror is serving the correct, unmodified files.
  4. Verify you are not mixing platform-specific wheels with hashes computed from a different platform's wheel.

Example fix

// before
pkg==1.0 --hash=sha256:aaaa...
  (actual download hashes to bbbb...)

// after
pkg==1.0 --hash=sha256:bbbb...
  (run: pip hash pkg-1.0-py3-none-any.whl to get bbbb)
Defensive patterns

Strategy: try-catch

Validate before calling

import hashlib

def verify_archive_hash(filepath: str, expected_sha256: str) -> bool:
    """Pre-verify a downloaded file's sha256 before handing to pip."""
    h = hashlib.sha256()
    with open(filepath, 'rb') as f:
        for chunk in iter(lambda: f.read(8192), b''):
            h.update(chunk)
    return h.hexdigest() == expected_sha256.lower()

Try / catch

from pip._internal.exceptions import HashMismatch

try:
    # pip install / download operation
    pass
except HashMismatch as e:
    # e has .allowed and .gotten attributes for diagnostics
    print(f'Hash mismatch: expected {e.allowed}, got {e.gotten}')
    # Re-download, regenerate lock file, or alert on potential tampering

Prevention

When it happens

Trigger: check_against_chunks / check_against_file is called after downloading an archive, iterates all expected hash algorithms, computes the actual digest, and if none match any entry in self._allowed[hash_name] for any algorithm, calls _raise at line 90. This happens during install with --require-hashes or when any requirement has a hash.

Common situations: Updating a package version in requirements.txt without regenerating its hash. A requirements file whose hashes were generated for a different file (different platform wheel vs sdist). Man-in-the-middle attack or compromised mirror serving a tampered package. Transitive dependency pinning changes that shift which file is downloaded.

Related errors


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

Appendix: 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 f399c37189)