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
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, the Date header and the @method, @authority, and @target-uri derived components are signed.
The Date header is set if it is absent, and Authorization is signed if present. The signer generates
and signs a SHA-256 Content-Digest for buffered request content, including empty content on GET requests, using
the format described in RFC 9530, Digest Fields.
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:
verify_result = HTTPSignatureAuth.verify(response,
signature_algorithm=algorithms.HMAC_SHA256,
key_resolver=MyKeyResolver())
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.
verify() reads and verifies the response body automatically. Use stream=True as shown above so it can
verify compressed content before Requests decodes it. After verification, use response.content, response.text,
or response.json() normally. verify_result.body contains the authenticated bytes before content decoding.
Verification buffers content in memory; it does 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 what is signed
It is important to understand and follow the best practice rule of “See what is signed” when verifying HTTP message signatures. The gist of this rule is: if your application neglects to verify that the information it trusts is what was actually signed, the attacker can supply a valid signature but point you to malicious data that wasn’t signed by that signature. Failure to follow this rule can lead to vulnerability against signature wrapping and substitution attacks.
In requests-http-signature, you can ensure that the information signed is what you expect to be signed by only trusting
the data returned by the verify() method:
verify_result = HTTPSignatureAuth.verify(message, ...)
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¶
Digest generation and validation use the shared http_message_signatures.ContentDigest implementation.
To generate SHA-512 instead of the default SHA-256, configure a signer subclass:
from http_message_signatures import ContentDigest, HTTPMessageSigner
class SHA512Digest(ContentDigest):
algorithm = "sha-512"
class SHA512Signer(HTTPMessageSigner):
content_digest_class = SHA512Digest
class MyAuth(HTTPSignatureAuth):
signer_class = SHA512Signer
signer_class replaces the former signing_content_digest_algorithm and add_digest() customization points.
Fixed keys use the upstream MappingKeyResolver; the package no longer provides SingleKeyResolver.
Signing and verification errors from the shared APIs propagate as HTTPMessageSignaturesException or its
InvalidSignature subclass. Requests-specific preparation errors raise RequestsHttpSignatureException.
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 requires use_existing_digest=True on the auth object
and is checked against buffered content before signing. This option is also required when signing a request again
or appending another signature. A stale digest is rejected; remove or replace it after changing the content.
File objects and iterators require a caller-supplied Content-Digest for the bytes that will be sent from their current
position and use_existing_digest=True. The auth handler validates the supplied digest’s format and algorithms
and signs the whole field without reading or rewinding the stream. For example:
import io
from http_message_signatures import ContentDigest
payload = b"upload content"
digest = ContentDigest.create(payload)
stream_auth = HTTPSignatureAuth(key=preshared_secret, key_id=preshared_key_id,
signature_algorithm=algorithms.HMAC_SHA256, use_existing_digest=True)
response = requests.post(url, data=io.BytesIO(payload), headers={"Content-Digest": digest},
auth=stream_auth, allow_redirects=False)
Keep a streaming body unchanged after computing its digest. Verification reads seekable binary request bodies from
the starting position recorded by Requests and restores their current position afterward. Non-seekable request bodies
cannot be verified. 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.
API documentation¶
- exception requests_http_signature.RequestsHttpSignatureException(*args, **kwargs)[source]¶
An error occurred while constructing the HTTP Signature for your request.
Initialize RequestException with request and response objects.
- Parameters:
args (Any)
kwargs (Any)
- Return type:
None
- class requests_http_signature.HTTPSignatureAuth(*, signature_algorithm, key=None, key_id, key_resolver=None, covered_component_ids=('@method', '@authority', '@target-uri'), label=None, tag=None, append_if_signature_exists=False, include_alg=True, use_nonce=False, expires_in=None, use_existing_digest=False)[source]¶
A Requests authentication plugin (
requests.auth.AuthBasesubclass) implementing RFC 9421, HTTP Message Signatures.- Parameters:
signature_algorithm (type[HTTPSignatureAlgorithm]) – One of
requests_http_signature.algorithms.HMAC_SHA256,requests_http_signature.algorithms.ECDSA_P256_SHA256,requests_http_signature.algorithms.ED25519,requests_http_signature.algorithms.RSA_PSS_SHA512, orrequests_http_signature.algorithms.RSA_V1_5_SHA256.key (bytes | None) – Key material that will be used to sign the request. In the case of HMAC, this should be the raw bytes of the shared secret; for all other algorithms, this should be the bytes of the PEM-encoded private key material.
key_id (str) – The key ID to use in the signature.
key_resolver (HTTPSignatureKeyResolver | None) – Instead of specifying a fixed key, you can instead pass a key resolver, which should be an instance of a subclass of
http_message_signatures.HTTPSignatureKeyResolver. A key resolver should have two methods,resolve_private_key(key_id)(required only for signing) andresolve_public_key(key_id)(required only for verifying). Your implementation should ensure that the key id is recognized and return the corresponding key material as PEM bytes, cryptography key objects, or shared secret bytes for HMAC. Omit bothkeyandkey_resolverwhen usingprepare()andapply()with an external signer.covered_component_ids (Sequence[str]) – Lowercase header names or serialized RFC 9421 component identifiers. Request derived components include
@method,@target-uri,@authority,@scheme,@request-target,@path,@query, and'"@query-param";name="Pet"'. Parameter values are case-sensitive; their order does not affect identity. Duplicate component identifiers are rejected. Defaults to@method,@authority, and@target-uri.AuthorizationandDateare also covered when present. The signer adds digest coverage for buffered content, including empty requests. Explicit digest-member coverage, such as'"content-digest";key="sha-256"', replaces automatic whole-field coverage for buffered content. Streams always cover the whole supplied Content-Digest field.label (str | None) – The signature dictionary label, which is not signed.
tag (str | None) – A signed application tag, usable with
verify(expect_tag=...).append_if_signature_exists (bool) – Append a distinct signature label instead of replacing existing signatures.
include_alg (bool) – By default, the signature parameters will include the
algparameter, using it to identify the signature algorithm. If you wish not to include this parameter, set this toFalse.use_nonce (bool) – Set this to
Trueto include a unique message-specific nonce in the signature parameters. The format of the nonce can be controlled by subclassing this class and overloading theget_nonce()method.expires_in (timedelta | None) – Use this to set the
expiressignature parameter to the time of signing plus the given timedelta.use_existing_digest (bool) – Allow a supplied Content-Digest, validating it against buffered bytes before signing. Required for streams with precomputed digests and when signing a request again.
- component_resolver_class¶
A subclass of
http_message_signatures.HTTPSignatureComponentResolvercan be used to override this value to customize the retrieval of header and derived component values if needed.alias of
HTTPSignatureComponentResolver
- verifier_class¶
A verifier subclass can configure creation-time requirements and clock-skew tolerance.
alias of
HTTPMessageVerifier
- signer_class¶
A signer subclass can select a ContentDigest subclass through its content_digest_class attribute.
alias of
HTTPMessageSigner
- prepare(request)[source]¶
Set digest/date headers and return signing input without resolving a key.
Use a fully prepared Requests request. Keep the returned object with this request while an external signer signs its
signature_basebytes.- Parameters:
request (PreparedRequest)
- Return type:
PreparedSignature
- apply(request, prepared, signature, *, append_if_signature_exists=None)[source]¶
Attach raw signature bytes and return the request ready for
Session.send.Covered values must still match preparation. Buffered bodies are checked against their digest; callers supplying stream digests must keep the stream unchanged. This does not verify the external signature or refresh its age.
- Parameters:
request (PreparedRequest)
prepared (PreparedSignature)
signature (bytes)
append_if_signature_exists (bool | None)
- Return type:
PreparedRequest
- classmethod get_body(message)[source]¶
Read content for digest verification, preserving buffered responses and seekable request bodies.
Compressed responses must be requested with
stream=True. Their encoded content is buffered internally and remains available to Requests for normal decoding. Seekable binary request bodies are read from the starting position recorded by Requests, then restored to their current position.- Parameters:
message (PreparedRequest | Response)
- Return type:
bytes
- classmethod verify(message, *, require_components=None, signature_algorithm, key_resolver, max_age=datetime.timedelta(days=1), expect_tag=None, expect_label=None)[source]¶
Verify an HTTP message signature and its body digest using content read from the message.
Request compressed responses with
stream=Trueso this method can read their encoded content. It buffers that content internally and preserves Requests’ normalcontent,text, andjson()access after verification. Already decoded or partially consumed response streams cannot be verified. Buffered requests and seekable binary request bodies are read automatically; seekable bodies retain their current position. Missing unencoded response content is treated as empty bytes.See what is signed
It is important to understand and follow the best practice rule of “See what is signed” when verifying HTTP message signatures. The gist of this rule is: if your application neglects to verify that the information it trusts is what was actually signed, the attacker can supply a valid signature but point you to malicious data that wasn’t signed by that signature. Failure to follow this rule can lead to vulnerability against signature wrapping and substitution attacks.
You can ensure that the information signed is what you expect to be signed by only trusting the VerifyResult tuple returned by
verify().- Parameters:
message (PreparedRequest | Response) –
The HTTP response or request to verify. You can either pass a received response, or reconstruct an arbitrary request using the Requests API:
request = requests.Request(...) prepared_request = request.prepare() HTTPSignatureAuth.verify(prepared_request, ...)
require_components (Sequence[str] | None) –
Lowercase header names or serialized component identifiers whose coverage is required. Parameter order is ignored for comparison, but parameter values retain their case and types. Duplicate identifiers fail. The request default is
("@method", "@authority", "@target-uri"). The response default is("@status", '"@method";req', '"@authority";req', '"@target-uri";req'), binding the response to its request. Supply a different profile explicitly if your peer signs other components.Nonempty content, or a present Content-Digest field, additionally requires a covered content digest. A selected digest dictionary member is sufficient. Empty messages do not require an absent digest; any present digest is still verified, including the digest of empty content. Request components with
;reqdo not satisfy coverage of the response body. Validation returns the bytes inresult.body.signature_algorithm (type[HTTPSignatureAlgorithm]) – The algorithm expected to be used by the signature. Any signature not using the expected algorithm will cause an
InvalidSignatureexception. Must be one ofrequests_http_signature.algorithms.HMAC_SHA256,requests_http_signature.algorithms.ECDSA_P256_SHA256,requests_http_signature.algorithms.ED25519,requests_http_signature.algorithms.RSA_PSS_SHA512, orrequests_http_signature.algorithms.RSA_V1_5_SHA256.key_resolver (HTTPSignatureKeyResolver) – A key resolver, which should be an instance of a subclass of
http_message_signatures.HTTPSignatureKeyResolver. A key resolver should have two methods,resolve_private_key(key_id)(required only for signing) andresolve_public_key(key_id)(required only for verifying). Your implementation should ensure that the key id is recognized and return the corresponding key material as PEM bytes (or shared secret bytes for HMAC).max_age (timedelta | None) – Maximum age since
created;Nonedisables this limit, but expiration is still checked. Customizeverifier_classto change itsrequire_createdandmax_clock_skewpolicies.expect_tag (str | None) – Select a signed application tag. Selection must leave one verified signature.
expect_label (str | None) – Further select the dictionary label; requires
expect_tagunder the default policy.
- Returns:
VerifyResult, a namedtuple with the following attributes:
label(str): The label for the signaturealgorithm: (same assignature_algorithmabove)covered_components: A mapping of component names to their values, as covered by the signatureparameters: A mapping of signature parameters to their values, as covered by the signature, including “alg”, “created”, “expires”, “keyid”, and “nonce”. To protect against replay attacks, retrieve the “nonce” parameter here and check that it has not been seen before.nonce: The signed nonce, orNone. Applications must enforce replay detection.body: Content bytes before decoding, after validating their covered digest;Noneotherwise.
- Raises:
InvalidSignature – Signature, required coverage, or body-digest validation fails.
HTTPMessageSignaturesException – The algorithm, required components, or selection policy is invalid.
- Return type:
VerifyResult