How to Verify PGP Signatures for Python Packages Downloaded with pip
Verify PGP signatures Python packages pip: PyPI hosts optional GPG .asc signatures for wheels/sdists. Pip verifies hashes automatically, but PGP confirms publisher authenticity vs supply-chain attacks (e.g., XZ Utils). Targets: “gpg verify pip wheel”, “PyPI PGP signature check”, “python package signature verification”.
Manual process: pip download → PyPI .asc → import key → gpg --verify.
Example: cryptography (security-critical, signed releases).
Why PGP Beyond Pip Hashes?
Snakes periodically shed their skin—a process called ecdysis—to remove parasites, heal damage, and reveal a fresh, healthy layer underneath. This isn’t just renewal; it’s a biological verification that the outer surface is intact and authentic.
Similarly, pip’s hash verification ensures the file hasn’t been corrupted in transit—it confirms the integrity of the file (the skin is unblemished). However, it doesn’t verify the authenticity of the source. A malicious actor could replace a package on a mirror with a tampered version that matches the expected hash in the index (a perfectly forged skin).
PGP signatures provide the missing layer: publisher authenticity. This is the “verification” step—ensuring the package was actually signed by the trusted maintainer, not an impostor.
Problem Framing: The Supply Chain Risk
PyPI’s standard security model uses SHA256 hashes stored in the package index. This ensures the file you download matches the file PyPI hosts. However, it does not guarantee that the file PyPI hosts was uploaded by the legitimate author. If an attacker compromises the author’s credentials or a PyPI mirror, they could upload a malicious package with a valid hash.
PGP signatures solve this by cryptographically binding the package to the author’s private key. When we verify the signature, we are asking: “Was this package signed by the key I trust for this project?”
Comparing Security Layers
Before we dive into the mechanics, here’s how pip hashes and PGP signatures complement each other:
| Security Layer | Purpose | Verifies |
|---|---|---|
| Pip Hashes | Integrity | File matches the hash in PyPI index (no tampering post-upload) |
| PGP Signatures | Authenticity | Publisher’s private key signed the file (legitimate source) |
When to Use PGP Verification
While pip hashes are sufficient for many use cases, PGP verification is critical for:
- High-security environments: PCI DSS, compliance audits, or zero-trust networks.
- Security-critical packages: Cryptography libraries (e.g.,
cryptography,requests). - Manual auditing: When you need to verify a specific version offline.
Note: Though not all packages on PyPI provide signatures—for instance,
requeststypically doesn’t—always check the project docs or PyPI files page for.ascfiles.
Prerequisites: Install GPG
# Ubuntu/Debian
sudo apt update && sudo apt install gnupg
# macOS
brew install gnupg
# Verify
gpg --version # 2.4+
Step 1: Download Package (No Install)
We begin by downloading the package file without installing it. This allows us to inspect the file and its signature independently. We use pip download with specific flags to control exactly what we get.
pip download --no-deps cryptography==42.0.5
# Downloads cryptography-42.0.5-py3-none-any.whl to current directory
Command breakdown:
-
--no-deps: Do not download dependencies (we only want the main package for this check). -
cryptography==42.0.5: Specifies the exact version to download.
After running this, verify the download:
$ ls *.whl
cryptography-42.0.5-py3-none-any.whl
$ pip hash cryptography-42.0.5-py3-none-any.whl
# Compare the SHA256 (and other) hashes to those listed on the PyPI files page
Step 2: Download .asc Signature from PyPI
Next, we need the corresponding PGP signature file (.asc). PyPI hosts these alongside the package files.
- Visit the package files page: https://pypi.org/project/cryptography/42.0.5/#files
- Locate and download the matching
.ascfile (e.g.,cryptography-42.0.5-py3-none-any.whl.asc).
Alternatively, you can download it via curl if you know the exact URL:
curl -O https://files.pythonhosted.org/packages/.../cryptography-42.0.5-py3-none-any.whl.asc
Why separate download? The signature file is distinct from the package file. It contains the cryptographic signature created by the publisher’s private key.
Step 3: Import Publisher’s Public Key
To verify a signature, we need the publisher’s public key in our local GPG keyring. We can retrieve this from a keyserver or import it directly from a file provided by the project.
For the cryptography package, the PyCA (Python Cryptographic Authority) maintains the signing key.
Key Information:
- Fingerprint:
F8E0 3785 0B2D 3C7F 0C2B 1855 16E2 0B19 1145 BD82 - Short ID:
1145BD82
Option A: Fetch from Keyserver
gpg --keyserver hkps://keys.openpgp.org \
--recv-keys 1145BD820F92900AF8E037850B2D3C7F0C2B185516E20B19
Option B: Import from Project File
If the project provides a .asc key file on their website:
wget https://cryptography.io/pubkey.asc
gpg --import pubkey.asc
Verification After importing, verify the key fingerprint matches the published value:
$ gpg --fingerprint 1145BD82
pub rsa4096 2016-03-24 [SC]
F8E0 3785 0B2D 3C7F 0C2B 1855 16E2 0B19 1145 BD82
uid [ unknown] PyCA Development <team@pyca.org>
sub rsa4096 2016-03-24 [E]
Tip: Always verify the fingerprint directly from the project’s official documentation or website, not from the keyserver output alone. This prevents “key spoofing” attacks where an attacker uploads a fake key with the same short ID.
Step 4: Verify Signature
Now we perform the actual verification. The gpg --verify command checks the signature file against the package file using the imported public key.
gpg --verify cryptography-42.0.5-py3-none-any.whl.asc cryptography-42.0.5-py3-none-any.whl
Command breakdown:
- First argument: The signature file (
.asc). - Second argument: The package file to verify.
Expected Output (Good Signature):
gpg: Signature made Tue 01 Jan 2024 12:00:00 PM UTC
gpg: using RSA key F8E0 3785 0B2D 3C7F 0C2B 1855 16E2 0B19 1145 BD82
gpg: Good signature from "PyCA Development <team@pyca.org>" [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
gpg: There is no indication that the signature belongs to the owner.
Primary key fingerprint: F8E0 3785 0B2D 3C7F 0C2B 1855 16E2 0B19 1145 BD82
Interpreting the Output:
Of course, you’ll see a “Good signature” confirming the crypto check passed—the file matches the signature from the key we imported.
The [unknown] and “not certified” warnings are normal; GPG requires manual trust for third-party keys via fingerprint verification—it doesn’t indicate invalidity.
Bad Signature Output:
gpg: BAD signature from "PyCA Development <team@pyca.org>" [unknown]
If you see this, do not use the package. It may be corrupted or tampered with.
Troubleshooting
Common issues and how to resolve them:
| Issue | Cause | Fix |
|---|---|---|
gpg: Can't check signature: No public key | The public key for the signer is not in your keyring. | Import the key (Step 3). |
gpg: BAD signature | The file content does not match the signature. | 1. Redownload both the .whl and .asc files.2. Verify file sizes match expectations. 3. Check PyPI hashes ( pip hash <file>). |
gpg: no valid OpenPGP data found | The .asc file is empty or corrupted. | Redownload the .asc file. |
No .asc file on PyPI | The project does not sign releases. | Rely on pip hashes; optionally request signatures from maintainers. |
| Multiple signatures | Package has multiple signers (e.g., co-maintainers). | Verify each signature matches a trusted key. |
Automating Verification
For repeated checks, you can automate the process with a simple script. Save this as verify-pkg.sh:
#!/bin/bash
# Usage: ./verify-pkg.sh <package_name> <version>
PKG=$1
VER=$2
FILENAME="${PKG//-/_}-${VER}-*.whl"
echo "Downloading $PKG==$VER..."
pip download --no-deps "$PKG==$VER"
# Note: This script assumes the .asc file is available and key is imported.
# In a real scenario, you'd need to fetch the .asc file URL dynamically.
echo "Manual steps required:"
echo "1. Download .asc file from PyPI."
echo "2. Run: gpg --verify ${FILENAME}.asc ${FILENAME}"
Note: Fully automating the download of
.ascfiles requires parsing PyPI’s JSON API, which is beyond the scope of this basic guide.
CI/CD: GitHub Actions PGP Check
Integrating PGP verification into CI/CD pipelines ensures that every build uses authenticated packages. Here is a basic example for GitHub Actions:
- name: Verify PGP Signature
run: |
sudo apt-get update && sudo apt-get install -y gnupg
gpg --keyserver hkps://keys.openpgp.org --recv-keys 1145BD820F92900AF8E037850B2D3C7F0C2B185516E20B19
pip download --no-deps cryptography==42.0.5
# Download .asc file (simplified for example)
curl -O https://files.pythonhosted.org/packages/.../cryptography-42.0.5-py3-none-any.whl.asc
gpg --verify cryptography-42.0.5-py3-none-any.whl.asc cryptography-42.0.5-py3-none-any.whl
Note: In production, you should cache the GPG key to avoid repeated keyserver requests, and use verified URLs for the .asc file.
Alternatives: Sigstore (Future)
PyPI is experimenting with Sigstore and Cosign for keyless signature verification. This approach uses ephemeral keys tied to identity providers (like GitHub OIDC) rather than long-lived PGP keys.
While PGP remains the standard for manual verification today, keep an eye on PyPI Trusted Publishers for future developments.
Best Practices
- Verify hashes first: Always run
pip hashon downloaded files and cross-check against PyPI before PGP verification—this confirms integrity independently. - Fingerprint from source: Obtain key fingerprints directly from project websites or release notes, not keyservers.
- Key management: Export trusted keys to a file for air-gapped/offline verification:
gpg --export --armor KEYID > trusted-keys.asc. - Team workflows: Share a signed keyring snapshot or use hardware keys for collective trust.
- Automation caveats: Scripts can’t fully automate
.ascfetching without PyPI API parsing; manual checks remain essential for security.
Conclusion
We have walked through verifying PGP signatures for Python packages, from downloading the files to importing keys and checking signatures. This process adds a critical layer of supply-chain security, ensuring that the packages you install are genuinely from the intended authors.
For security-critical projects, make PGP verification a standard part of your manual audit process. While pip handles integrity automatically, PGP confirms authenticity.
Related Reading:
Next Step: Try verifying a signature yourself:
pip download cryptography Sponsored by Durable Programming
Need help maintaining or upgrading your Python application? Durable Programming specializes in keeping Python apps secure, performant, and up-to-date.
Hire Durable Programming