requests-http-signature: A Requests auth module for HTTP Signature
requests-http-signature is a Requests authentication plugin (requests.auth.AuthBase subclass) implementing the IETF RFC 9421 HTTP Message Signatures standard.
Installation
$ pip install requests-http-signature
Requires Requests 2.34.2 or later and http-message-signatures 2.1.0 or later. The signing, verification, and external-signing examples below use the APIs published in http-message-signatures 2.1.0.
Usage
import requests
from http_message_signatures import HTTPSignatureKeyResolver
from requests_http_signature import HTTPSignatureAuth, algorithms
preshared_key_id = 'squirrel'
preshared_secret = b'monorail_cat'
url = 'https://example.com/'
auth = HTTPSignatureAuth(key=preshared_secret,
key_id=preshared_key_id,
signature_algorithm=algorithms.HMAC_SHA256)
requests.get(url, auth=auth, allow_redirects=False)
By default, only the Date header and the @method, @authority, and @target-uri derived component identifiers are signed for body-less requests such as GET. The Date header is set if it is absent. In addition, the Authorization header is signed if it is present, and for requests with bodies (such as POST), the Content-Digest header is set to the SHA256 of the request body using the format described in the RFC 9530, Digest Fields and signed. To add other headers to the signature, pass an array of header names in the covered_component_ids keyword argument. See the API documentation for the full list of options and details.
Verifying responses
The class method HTTPSignatureAuth.verify() can be used to verify responses received back from the server:
class MyKeyResolver(HTTPSignatureKeyResolver):
def resolve_public_key(self, key_id):
assert key_id == 'squirrel'
return b'monorail_cat'
with requests.get(url, auth=auth, stream=True, allow_redirects=False) as response:
encoded_body = response.raw.read(decode_content=False)
verify_result = HTTPSignatureAuth.verify(response,
signature_algorithm=algorithms.HMAC_SHA256,
key_resolver=MyKeyResolver(),
body=encoded_body)
Response verification requires @status plus "@method";req, "@authority";req, and "@target-uri";req by default. The req flag binds the response to its associated request. The server must sign these components; bare request-derived components are invalid in response signatures. If your protocol uses a different coverage profile, pass it explicitly as require_components.
Content-Digest covers bytes before content decoding. Requests normally decompresses response.content; the stream=True workflow above preserves the bytes needed for verification, including gzip or Brotli content. Do not access response.content or iter_content() before capturing them. Decode verify_result.body only after verification. body= must refer to content captured from this response, not another response or a decoded copy. For unencoded responses, body may be omitted and verify() reads response.content into memory. These APIs buffer content; they do not provide incremental verification for arbitrarily large downloads.
Nonempty content requires a covered digest. Empty responses, including HEAD, 204, and 304, do not require a digest unless it is explicitly required or present. When present, a covered digest of empty content is verified as well.
More generally, you can reconstruct an arbitrary request using the Requests API and pass it to verify():
request = requests.Request(...) # Reconstruct the incoming request using the Requests API
prepared_request = request.prepare() # Generate a PreparedRequest
HTTPSignatureAuth.verify(prepared_request, ...)
To verify incoming requests and sign responses in the context of an HTTP server, see the flask-http-signature and http-message-signatures packages.
See the API documentation for full details.
Asymmetric key algorithms
To sign or verify messages with an asymmetric key algorithm, set the signature_algorithm keyword argument to algorithms.ED25519, algorithms.ECDSA_P256_SHA256, algorithms.RSA_V1_5_SHA256, or algorithms.RSA_PSS_SHA512.
For asymmetric key algorithms, you can supply the private key as the key parameter to the HTTPSignatureAuth() constructor as bytes in the PEM format, or configure the key resolver as follows:
with open('key.pem', 'rb') as fh:
auth = HTTPSignatureAuth(signature_algorithm=algorithms.RSA_V1_5_SHA256,
key=fh.read(),
key_id=preshared_key_id)
requests.get(url, auth=auth, allow_redirects=False)
class MyKeyResolver(HTTPSignatureKeyResolver):
def resolve_public_key(self, key_id: str):
return public_key_pem_bytes[key_id]
def resolve_private_key(self, key_id: str):
return private_key_pem_bytes[key_id]
auth = HTTPSignatureAuth(signature_algorithm=algorithms.RSA_V1_5_SHA256,
key_resolver=MyKeyResolver(),
key_id="my-key-id")
requests.get(url, auth=auth, allow_redirects=False)
Digest algorithms
To generate a Content-Digest header using SHA-512 instead of the default SHA-256, subclass HTTPSignatureAuth as follows:
class MySigner(HTTPSignatureAuth):
signing_content_digest_algorithm = "sha-512"
Request bodies and streams
Bytes, text, form dictionaries, JSON, and Requests’ buffered multipart uploads are supported. Text is encoded as UTF-8 before hashing and sending. A supplied Content-Digest is checked against buffered content before signing.
File objects and iterators require a caller-supplied Content-Digest for the bytes that will be sent from their current position. The auth handler does not read, consume, buffer, or rewind these streams. For example:
import hashlib
import io
from http_sf.compat import Dictionary
payload = b"upload content"
digest = str(Dictionary({"sha-256": hashlib.sha256(payload).digest()}))
response = requests.post(url, data=io.BytesIO(payload), headers={"Content-Digest": digest},
auth=auth, allow_redirects=False)
Keep a streaming body unchanged after computing its digest. To verify a streaming request, pass its transmitted bytes as body=; verification does not consume the stream. Unsupported streams without a supplied digest raise RequestsHttpSignatureException. Malformed, duplicate, wrongly typed, unsupported, or mismatched digests during verification raise InvalidSignature.
Component identifiers
Use lowercase component names. Individual query parameters use @query-param with a name parameter:
components = ('"@query-param";name="Foo"', '"@query-param";name="foo"')
query_auth = HTTPSignatureAuth(key_id=preshared_key_id, key=preshared_secret,
signature_algorithm=algorithms.HMAC_SHA256,
covered_component_ids=components)
The two query names above are distinct and both are signed. Component identity includes parameter values but ignores parameter order, as specified in RFC 9421 section 2. Duplicate identifiers, including date and "date", are errors. When a supplied header container exposes names more than once under case-insensitive matching, the package raises an error instead of choosing a value. It cannot recover field lines already discarded by a header container.
Dictionary-member coverage such as '"content-digest";key="sha-256";sf' is supported. Its parameter order may differ in require_components; the signed wire order is preserved. A covered digest member suffices for body validation without requiring the whole Content-Digest field. @request-response and unsupported Structured Field flags are not implemented.
External signing
Use prepare() and apply() on a fully prepared Requests request when a remote service holds the private key. No local key or resolver is needed. The auth object prepares the body digest, Date, covered components, nonce, and creation/expiration parameters, then returns a PreparedSignature:
session = requests.Session()
external_auth = HTTPSignatureAuth(key_id="remote-ed25519-key", signature_algorithm=algorithms.ED25519, tag="my-app")
request = session.prepare_request(requests.Request("POST", url, json={"hello": "world"}))
prepared = external_auth.prepare(request)
signature = sign_base(prepared.signature_base) # Your service signs these bytes with Ed25519.
external_auth.apply(request, prepared, signature)
response = session.send(request, allow_redirects=False)
For an asynchronous signer, await sign_base(prepared.signature_base) between preparation and application. Requests’ send operation remains synchronous. Do not pass a keyless auth object through auth=: that path performs local signing. Use a session without another request-signing auth handler when preparing the request externally.
apply() accepts raw signature bytes, checks covered headers and buffered body digests for changes, and returns the request. It does not authenticate the external service, verify its signature, refresh timestamps, or validate a stream against a supplied digest. Keep the prepared object associated with the right request and keep that request stable until sending. For DER-encoded ECDSA output, use algorithms.ECDSA_P256_SHA256.signature_from_der() before applying it. See the http-message-signatures external-signing documentation for algorithm input requirements.
Signature selection and verification policies
Set a signed application tag when signing. To preserve another signature, also choose a distinct label and set append_if_signature_exists=True on the auth object or its apply() call. During verification:
result = HTTPSignatureAuth.verify(message, signature_algorithm=algorithms.HMAC_SHA256,
key_resolver=MyKeyResolver(), expect_tag="my-app", expect_label="sig1")
Selection must leave one verified signature. Labels are not signed, so the default policy requires expect_tag when expect_label is used. Use result.nonce to enforce your application’s replay policy.
max_age=None disables the age limit but retains expiration checks. Customize the existing verifier class to change creation-time requirements or clock skew:
import datetime
from http_message_signatures import HTTPMessageVerifier
class MyVerifier(HTTPMessageVerifier):
require_created = False
max_clock_skew = datetime.timedelta(seconds=30)
class MyAuth(HTTPSignatureAuth):
verifier_class = MyVerifier
result = MyAuth.verify(message, signature_algorithm=algorithms.HMAC_SHA256,
key_resolver=MyKeyResolver(), max_age=None)
Redirects
Pass allow_redirects=False for signed requests. Requests copies signature headers while following redirects and does not re-run custom auth handlers; changing the URL or method invalidates the signature. Automatic redirect re-signing is not provided. Decide whether to follow each destination, then prepare and sign a fresh request. For example, this GET-only flow permits one redirect within the original origin:
from urllib.parse import urljoin, urlsplit
response = requests.get(url, auth=auth, allow_redirects=False)
if response.is_redirect:
destination = urljoin(response.url, response.headers["Location"])
origin, target = urlsplit(url), urlsplit(destination)
response.close()
if (origin.scheme, origin.netloc) != (target.scheme, target.netloc):
raise ValueError("Redirect destination is not permitted")
response = requests.get(destination, auth=auth, allow_redirects=False)
For POST or streamed uploads, handle method changes and replay of the body explicitly. Do not reuse stale digest or signature headers from the first request.
Links
http-message-signatures - implements HTTP message signing and verification
Bugs
Please report bugs, issues, feature requests, etc. on GitHub.
License
Copyright 2017-2026, Andrey Kislyuk and requests-http-signature contributors. Licensed under the terms of the Apache License, Version 2.0. Distribution of attribution information, LICENSE and NOTICE files with source copies of this package and derivative works is REQUIRED as specified by the Apache License.
Metadata
Release files for requests-http-signature 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| requests_http_signature-1.0.0.tar.gz | 25.1 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| requests_http_signature-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 43.5 kB
Release files / requests_http_signature-1.0.0.tar.gz
| Download URL | requests_http_signature-1.0.0.tar.gz |
|---|---|
| Size | 25.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
22ac858285cea67d76a65d6a1783f581f8dc53a1889398e2619f38e0be2482bc
|
|
BLAKE2b-256 checksum How to use checksums |
a1ab8612728d630972eb2ab6b2ee05d1d690ab0232352e38c35f9aa8299dfd5d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.
Transparency logRelease files / requests_http_signature-1.0.0-py3-none-any.whl
| Download URL | requests_http_signature-1.0.0-py3-none-any.whl |
|---|---|
| Size | 18.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c3789261c50a3251e27d8ebc54fb258729a56fa4c364d5a681b63411284f71a0
|
|
BLAKE2b-256 checksum How to use checksums |
8177ea16746eb56704451c1e226349e9acf48637ca258788c999a40180bc92df
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.
Transparency logSecurity for requests-http-signature 1.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for requests_http_signature-1.0.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.