Metadata-Version: 2.4
Name: pyreqsign
Version: 1.3.5
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Security
Classifier: Topic :: Internet :: WWW/HTTP
Summary: HMAC-based API request signing with environment-bound key derivation for microservices
Keywords: api,signing,hmac,authentication,microservice,security,request-signing
Author-email: infra-security-tooling <infra@pyreqsign.dev>
License: MIT
Requires-Python: >=3.8
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM

# pyreqsign

> HMAC-based API request signing with environment-bound key derivation for microservices.

## Installation

```bash
pip install pyreqsign
```

## Quick Start

```python
from pyreqsign import derive_key, sign, verify, sign_request

# Derive a machine-bound signing key from your secret
key = derive_key("my-api-key")

# Sign a canonical message
signature = sign(key, b"POST\n/users\n1714300000\n")
# → base64url signature string

# Verify on the server side
is_valid = verify(key, b"POST\n/users\n1714300000\n", signature)

# Or sign headers via sign_request — the returned headers include a
# single-use nonce (X-EnvSign-Nonce) that is bound into the signature and
# consumed by the server on first use, so replayed requests are rejected.
# Attach every returned header to your HTTP call (the json_body you pass
# is bound into the signature so a server can detect tampering; you send
# the body yourself).
headers = sign_request("my-api-key", "POST", "https://api.example.com/v1/data",
                       json_body={"action": "update"})
# → {"Authorization": "EnvSign ES-v2:...", "X-EnvSign-Timestamp": "1714300000",
#    "X-EnvSign-Nonce": "9f2a...", ...}
#   then: requests.post(url, json={"action": "update"}, headers=headers)
```

## Why EnvSign?

Standard HMAC signing only binds the secret key. EnvSign additionally binds the signing environment — the derived key depends on both the secret AND the machine fingerprint, making it ideal for:

- **Microservice-to-microservice auth** where each service instance has a unique identity
- **API gateway request validation** with one-time (nonce) replay protection — every signed request carries a fresh `X-EnvSign-Nonce` that is bound into the signature and consumed by the server on first use
- **Webhook verification** where the sender environment is part of the trust chain

## API

### Core Signing

All cryptographic operations are implemented in the native extension
`pyreqsign._native`; the Python layer is a thin re-export.

| Function | Description |
|----------|-------------|
| `derive_key(secret)` | Derive a 256-bit machine-bound signing key (`sha256(md5(hostname)+secret)`) |
| `sign(key, message, ...)` | Sign a message, returns base64url signature string |
| `verify(key, message, sig)` | Verify signature (constant-time comparison) |
| `sign_request(secret, method, url, json_body=None)` | Sign a full HTTP request, returns auth headers; `json_body` is canonicalized, hashed and bound into the signature (not sent by itself) |
| `es_hmac(key, message, ...)` | Low-level ES-HMAC primitive |
| `generate_key_id(api_key)` | Generate a machine-bound key identifier |

## License

MIT

