[](https://github.com/Mari6814/py-rfc3230-digest-headers/actions/workflows/ci.yml)
[](https://github.com/Mari6814/py-rfc3230-digest-headers/raw/main/badges/coverage.svg)
[](https://github.com/Mari6814/py-rfc3230-digest-headers/raw/main/badges/python-versions.svg)
# Introduction
A small library to provide the server and client side methods to require, negotiation and generate `Digest` HTTP headers as per [RFC 3230](https://datatracker.ietf.org/doc/html/rfc3230).
Clients can generate `Digest` headers of the form: `Digest: SHA-256=xyz, MD5=abc`. Server can require certain algorithms by sending `Want-Digest` headers of the form: `Want-Digest: SHA-256, SHA;q=0.5, MD5;q=0`.
# Installation
Install using pip:
```bash
pip install rfc3230-digest-headers
```
# Overview of the protocol
The protocol works as follows:
1. Client and server agree on what the `instance` bytes are for the endpoint in question. Usually the request body or the content of the resource before applying transformations.
2. Client sends request
3. If the client did not directly send a valid `Digest`, the server responds with `Want-Digest` header to indicate which algorithms it supports.
- Form of the `Want-Digest` header: `Want-Digest: SHA-256, SHA;q=0.5, MD5;q=0`
- The server can specify `qvalues` to indicate preference of algorithms.
- No value equals `q=1.0`.
- `q=0` means "do not use this algorithm".
4. Client generates `Digest` header using one of the supported algorithms and sends it in the request.
- Form of the `Digest` header: `Digest: SHA-256=xyz, MD5=abc`
5. Server verifies the `Digest` header and processes the request.
# Usage
The library provides only one enum class, `DigestHeaderAlgorithm`, which can be used by server and client to fully specify, negotiate and generate `Digest` HTTP headers.
You do not use these algorithms directly, but instead have to use a couple of _static_ methods provided by the enum class.
## Example: Generate a `Digest` header
The client generates a `Digest` for their _instance_.
```python
from rfc3230_digest_headers import DigestHeaderAlgorithm
instance_bytes = b"Hello, World!"
header = DigestHeaderAlgorithm.make_digest_header(
instance=instance_bytes,
algorithms=[DigestHeaderAlgorithm.SHA256, DigestHeaderAlgorithm.MD5]
)
print(header.header_name) # "Digest"
print(header.header_value) # "SHA-256=..., MD5=..."
```
## Usage: Verify a `Digest` header
The server receives a request with a `Digest` header and verifies it.
```python
from rfc3230_digest_headers import DigestHeaderAlgorithm
instance_bytes = b"Hello, World!"
request_headers = {"Digest": "SHA-256=..., MD5=..."}
is_valid, want_digest_header_should_be_added = DigestHeaderAlgorithm.verify_request(
request_headers=request_headers,
instance=instance_bytes,
qvalues={
DigestHeaderAlgorithm.SHA256: 1.0,
DigestHeaderAlgorithm.SHA: 0.5,
DigestHeaderAlgorithm.MD5: 0.0 # If the client sends MD5, they will receive an error
},
)
print(is_valid) # True if the Digest header is valid
print(want_digest_header_should_be_added) # None if valid, otherwise contains the `Want-Digest` header to be sent to the client for negotiation
```
## Usage: Server-side negotiation of algorithms
The server can indicate which algorithms the endpoint requires by sending a `Want-Digest` header. The header is automatically generated when attempting to verify invalid request headers. In the following example, the client sends a `Digest` header with an unsupported algorithm (`MD5` with a _q-value_ of `0.0`), so the server responds with a `Want-Digest` header indicating which algorithms are supported.
```python
from rfc3230_digest_headers import DigestHeaderAlgorithm
# Fake request from client without an invalid Digest header
instance_bytes = b"Hello, World!"
request_headers = {"Digest": "SHA-256=..., MD5=..."}
is_valid, want_digest_header_should_be_added = DigestHeaderAlgorithm.verify_request(
request_headers=request_headers,
instance=instance_bytes,
qvalues={
DigestHeaderAlgorithm.SHA256: 1.0,
DigestHeaderAlgorithm.SHA: 0.5,
DigestHeaderAlgorithm.MD5: 0.0 # If the client sends MD5, they will receive an error
},
)
if want_digest_header_should_be_added:
print(want_digest_header_should_be_added.header_name) # "Want-Digest"
print(want_digest_header_should_be_added.header_value) # "SHA-256, SHA;q=0.5, MD5;q=0"
# Send the response with the generated Want-Digest header
...
```
## Usage: Client-side negotiation of algorithms
When an endpoint responds with a `Want-Digest` header, the client can parse it and generate a valid `Digest` header. In the following example, imagine that we initially sent a request with `b'Hello, World!'` as body, and the server responded with an HTTP error code and a `Want-Digest` header. The client sees that its original request failed, and that the server wants a `Digest` header. The client then generates a valid `Digest` header using the highest priority algorithm from the `Want-Digest` header and re-sends the request.
```python
from rfc3230_digest_headers import DigestHeaderAlgorithm
# Fake response from server with Want-Digest header
instance_bytes = b"Hello, World!"
want_digest_header_value = "SHA-256, SHA;q=0.5, MD5;q=0"
# Parse the response and generate a valid Digest header (if possible)
header = DigestHeaderAlgorithm.handle_want_digest_header(
instance=instance_bytes,
want_digest_header=want_digest_header_value,
algorithms="auto" # Use the highest priority algorithm from Want-Digest
)
print(header.header_name) # "Digest"
print(header.header_value) # "sha-256=..."
# re-send the request with the generated Digest header
...
```
Raw data
{
"_id": null,
"home_page": null,
"name": "rfc3230-digest-headers",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.10",
"maintainer_email": null,
"keywords": "authentication, digest, headers, http, integrity, rfc3230, security",
"author": null,
"author_email": "Marian Plivelic <marianplivelic@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/ab/bd/2ae1e55d3bf587a3ba981e7f4e5025a2d284d21c3b1816e00b504662b148/rfc3230_digest_headers-1.0.3.tar.gz",
"platform": null,
"description": "[](https://github.com/Mari6814/py-rfc3230-digest-headers/actions/workflows/ci.yml)\n[](https://github.com/Mari6814/py-rfc3230-digest-headers/raw/main/badges/coverage.svg)\n[](https://github.com/Mari6814/py-rfc3230-digest-headers/raw/main/badges/python-versions.svg)\n\n# Introduction\n\nA small library to provide the server and client side methods to require, negotiation and generate `Digest` HTTP headers as per [RFC 3230](https://datatracker.ietf.org/doc/html/rfc3230).\nClients can generate `Digest` headers of the form: `Digest: SHA-256=xyz, MD5=abc`. Server can require certain algorithms by sending `Want-Digest` headers of the form: `Want-Digest: SHA-256, SHA;q=0.5, MD5;q=0`.\n\n# Installation\n\nInstall using pip:\n\n```bash\npip install rfc3230-digest-headers\n```\n\n# Overview of the protocol\n\nThe protocol works as follows:\n\n1. Client and server agree on what the `instance` bytes are for the endpoint in question. Usually the request body or the content of the resource before applying transformations.\n2. Client sends request\n3. If the client did not directly send a valid `Digest`, the server responds with `Want-Digest` header to indicate which algorithms it supports.\n - Form of the `Want-Digest` header: `Want-Digest: SHA-256, SHA;q=0.5, MD5;q=0`\n - The server can specify `qvalues` to indicate preference of algorithms.\n - No value equals `q=1.0`.\n - `q=0` means \"do not use this algorithm\".\n4. Client generates `Digest` header using one of the supported algorithms and sends it in the request.\n - Form of the `Digest` header: `Digest: SHA-256=xyz, MD5=abc`\n5. Server verifies the `Digest` header and processes the request.\n\n# Usage\n\nThe library provides only one enum class, `DigestHeaderAlgorithm`, which can be used by server and client to fully specify, negotiate and generate `Digest` HTTP headers.\nYou do not use these algorithms directly, but instead have to use a couple of _static_ methods provided by the enum class.\n\n## Example: Generate a `Digest` header\n\nThe client generates a `Digest` for their _instance_.\n\n```python\nfrom rfc3230_digest_headers import DigestHeaderAlgorithm\n\ninstance_bytes = b\"Hello, World!\"\nheader = DigestHeaderAlgorithm.make_digest_header(\n instance=instance_bytes,\n algorithms=[DigestHeaderAlgorithm.SHA256, DigestHeaderAlgorithm.MD5]\n)\nprint(header.header_name) # \"Digest\"\nprint(header.header_value) # \"SHA-256=..., MD5=...\"\n```\n\n## Usage: Verify a `Digest` header\n\nThe server receives a request with a `Digest` header and verifies it.\n\n```python\nfrom rfc3230_digest_headers import DigestHeaderAlgorithm\n\ninstance_bytes = b\"Hello, World!\"\nrequest_headers = {\"Digest\": \"SHA-256=..., MD5=...\"}\nis_valid, want_digest_header_should_be_added = DigestHeaderAlgorithm.verify_request(\n request_headers=request_headers,\n instance=instance_bytes,\n qvalues={\n DigestHeaderAlgorithm.SHA256: 1.0,\n DigestHeaderAlgorithm.SHA: 0.5,\n DigestHeaderAlgorithm.MD5: 0.0 # If the client sends MD5, they will receive an error\n },\n)\nprint(is_valid) # True if the Digest header is valid\nprint(want_digest_header_should_be_added) # None if valid, otherwise contains the `Want-Digest` header to be sent to the client for negotiation\n```\n\n## Usage: Server-side negotiation of algorithms\n\nThe server can indicate which algorithms the endpoint requires by sending a `Want-Digest` header. The header is automatically generated when attempting to verify invalid request headers. In the following example, the client sends a `Digest` header with an unsupported algorithm (`MD5` with a _q-value_ of `0.0`), so the server responds with a `Want-Digest` header indicating which algorithms are supported.\n\n```python\nfrom rfc3230_digest_headers import DigestHeaderAlgorithm\n\n# Fake request from client without an invalid Digest header\ninstance_bytes = b\"Hello, World!\"\nrequest_headers = {\"Digest\": \"SHA-256=..., MD5=...\"}\nis_valid, want_digest_header_should_be_added = DigestHeaderAlgorithm.verify_request(\n request_headers=request_headers,\n instance=instance_bytes,\n qvalues={\n DigestHeaderAlgorithm.SHA256: 1.0,\n DigestHeaderAlgorithm.SHA: 0.5,\n DigestHeaderAlgorithm.MD5: 0.0 # If the client sends MD5, they will receive an error\n },\n)\nif want_digest_header_should_be_added:\n print(want_digest_header_should_be_added.header_name) # \"Want-Digest\"\n print(want_digest_header_should_be_added.header_value) # \"SHA-256, SHA;q=0.5, MD5;q=0\"\n # Send the response with the generated Want-Digest header\n ...\n```\n\n## Usage: Client-side negotiation of algorithms\n\nWhen an endpoint responds with a `Want-Digest` header, the client can parse it and generate a valid `Digest` header. In the following example, imagine that we initially sent a request with `b'Hello, World!'` as body, and the server responded with an HTTP error code and a `Want-Digest` header. The client sees that its original request failed, and that the server wants a `Digest` header. The client then generates a valid `Digest` header using the highest priority algorithm from the `Want-Digest` header and re-sends the request.\n\n```python\nfrom rfc3230_digest_headers import DigestHeaderAlgorithm\n\n# Fake response from server with Want-Digest header\ninstance_bytes = b\"Hello, World!\"\nwant_digest_header_value = \"SHA-256, SHA;q=0.5, MD5;q=0\"\n\n# Parse the response and generate a valid Digest header (if possible)\nheader = DigestHeaderAlgorithm.handle_want_digest_header(\n instance=instance_bytes,\n want_digest_header=want_digest_header_value,\n algorithms=\"auto\" # Use the highest priority algorithm from Want-Digest\n)\nprint(header.header_name) # \"Digest\"\nprint(header.header_value) # \"sha-256=...\"\n\n# re-send the request with the generated Digest header\n...\n```\n",
"bugtrack_url": null,
"license": null,
"summary": "Implementation of the Digest HTTP headers according to RFC 3230.",
"version": "1.0.3",
"project_urls": {
"Documentation": "https://github.com/Mari6814/py-rfc3230-digest-headers",
"Repository": "https://github.com/Mari6814/py-rfc3230-digest-headers"
},
"split_keywords": [
"authentication",
" digest",
" headers",
" http",
" integrity",
" rfc3230",
" security"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "88fd154586f6a6480a415bf73dffc58ddcb0845a54d9060f4396cf505fc7cc87",
"md5": "41a8f13d896fcdd67ab3c85db556131f",
"sha256": "91f853228a3f131adf573f37ed8c554064c51b936d189b12f8d0ed6dfcf404f2"
},
"downloads": -1,
"filename": "rfc3230_digest_headers-1.0.3-py3-none-any.whl",
"has_sig": false,
"md5_digest": "41a8f13d896fcdd67ab3c85db556131f",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.10",
"size": 9126,
"upload_time": "2025-10-08T20:50:14",
"upload_time_iso_8601": "2025-10-08T20:50:14.844089Z",
"url": "https://files.pythonhosted.org/packages/88/fd/154586f6a6480a415bf73dffc58ddcb0845a54d9060f4396cf505fc7cc87/rfc3230_digest_headers-1.0.3-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "abbd2ae1e55d3bf587a3ba981e7f4e5025a2d284d21c3b1816e00b504662b148",
"md5": "eedeff9fc385bfca167174d4b06876a1",
"sha256": "728c8d2734a2edaf289ba185625ef9c43b6e132f22d58188a9996ea3a27979b6"
},
"downloads": -1,
"filename": "rfc3230_digest_headers-1.0.3.tar.gz",
"has_sig": false,
"md5_digest": "eedeff9fc385bfca167174d4b06876a1",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10",
"size": 8048,
"upload_time": "2025-10-08T20:50:16",
"upload_time_iso_8601": "2025-10-08T20:50:16.302859Z",
"url": "https://files.pythonhosted.org/packages/ab/bd/2ae1e55d3bf587a3ba981e7f4e5025a2d284d21c3b1816e00b504662b148/rfc3230_digest_headers-1.0.3.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-10-08 20:50:16",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "Mari6814",
"github_project": "py-rfc3230-digest-headers",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "rfc3230-digest-headers"
}