Metadata-Version: 2.4
Name: fastapi_payloadshield
Version: 1.2.4
Summary: Pluggable FastAPI decorators for encrypting/decrypting request and response payloads
Author-email: Ganesh Kandu <kanduganesh@gmail.com>
License-Expression: Apache-2.0
Project-URL: Homepage, https://github.com/PayloadShield/FastAPIPS
Project-URL: Repository, https://github.com/PayloadShield/FastAPIPS.git
Project-URL: Issues, https://github.com/PayloadShield/FastAPIPS/issues
Keywords: fastapi,base64,crypto,encryption,decorator
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.7
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: Operating System :: OS Independent
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Framework :: FastAPI
Requires-Python: >=3.7
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: fastapi>=0.68.0
Requires-Dist: starlette>=0.19.0
Requires-Dist: compyps>=1.0.0
Provides-Extra: dev
Requires-Dist: pytest>=6.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.18.0; extra == "dev"
Dynamic: license-file

# FastAPI Payload Shield

Pluggable FastAPI decorators for encrypting and decrypting request and
response payloads. Configure your keys once, then annotate any route with
`@PayloadShield.encrypt`, `@PayloadShield.decrypt`, or `@PayloadShield.crypt`.

## Key Features

- **Pluggable encryption**: base64, Fernet, AES-GCM-256, ChaCha20-Poly1305,
  Hybrid RSA+AES, ECDH+AES-GCM, ECIES, and HPKE (RFC 9180) ship out of the
  box; register your own with `register_handler(...)`.- **One-time key configuration**: `PayloadShieldEnc.init({...})` sets keys
  globally for all decorators.
- **Route-agnostic**: no changes needed to your route logic besides adding a
  decorator.
- **Async-friendly**: works with FastAPI's async route handlers.

## Installation

```bash
pip install fastapi_payloadshield
```

## Quick Start

```python
from fastapi import FastAPI
from fastapi_payloadshield import PayloadShield, PayloadShieldEnc

# Configure encryption keys once, at startup.
PayloadShieldEnc.init({
    "Key": "my-symmetric-key",
})

app = FastAPI()

@app.get("/api/data")
@PayloadShield.encrypt("base64")
async def get_data():
    return {"message": "hello", "data": "world"}

@app.post("/api/process")
@PayloadShield.decrypt("base64")
async def process_data(data: dict):
    return {"received": data, "status": "success"}

@app.post("/api/secure")
@PayloadShield.crypt("base64")
async def secure_endpoint(data: dict):
    return {"processed": data}
```

## Initialization: `PayloadShieldEnc.init(...)`

Call once before serving requests. Every decorator reads this shared
configuration at call time.

```python
PayloadShieldEnc.init({
    "Key": key,               # symmetric key: fernet, aes-gcm-256, chacha20-poly1305
    "PrivateKey": "string",   # RSA/hybrid private key (file path or PEM content)
    "PublicKey": "string",    # RSA/hybrid public key (file path or PEM content)
    "ECPrivateKey": "string", # EC (P-256) private key (file path or PEM content)
    "ECPublicKey": "string",  # EC (P-256) public key (file path or PEM content)
    "HPKEPrivateKey": "string", # X25519 private key (file path or PEM content)
    "HPKEPublicKey": "string",  # X25519 public key (file path or PEM content)
})
```

| Field | Used by | Accepts |
|---|---|---|
| `Key` | `fernet`, `aes-gcm-256`, `chacha20-poly1305` | Raw key string. `aes-gcm-256` and `chacha20-poly1305` require the key to resolve to exactly 32 bytes (UTF-8 or base64 encoded). |
| `PrivateKey` | `rsa-hybrid` (decrypt) | File path to a PEM file, or the raw PEM content (RSA key). |
| `PublicKey` | `rsa-hybrid` (encrypt) | File path to a PEM file, or the raw PEM content (RSA key). |
| `ECPrivateKey` | `ecdh-aes-gcm`, `ecies` (decrypt) | File path to a PEM file, or the raw PEM content (EC P-256 key). |
| `ECPublicKey` | `ecdh-aes-gcm`, `ecies` (encrypt) | File path to a PEM file, or the raw PEM content (EC P-256 key). |
| `HPKEPrivateKey` | `hpke` (decrypt) | File path to a PEM file, or the raw PEM content (X25519 key). |
| `HPKEPublicKey` | `hpke` (encrypt) | File path to a PEM file, or the raw PEM content (X25519 key). |

Only set the fields required by the encryption types you actually use.

## Decorators

All three live on the `PayloadShield` class and take an `encryption_type`
(default `"base64"`).

### `@PayloadShield.encrypt(encryption_type)`

Encrypts the response payload only.

```python
@app.get("/api/users")
@PayloadShield.encrypt("base64")
async def get_users():
    return [{"id": 1, "name": "Alice"}]

# Response: {"encrypted": "W3siaWQiOiAxLCAibmFtZSI6ICJBbGljZSJ9XQ=="}
```

### `@PayloadShield.decrypt(encryption_type)`

Decrypts the request payload only; the route receives the decrypted dict.

```python
@app.post("/api/login")
@PayloadShield.decrypt("base64")
async def login(credentials: dict):
    return {"status": "success"}

# Expects: {"encrypted": "base64_encoded_json"}
```

### `@PayloadShield.crypt(encryption_type)`

Decrypts the request and encrypts the response.

```python
@app.post("/api/secure")
@PayloadShield.crypt("base64")
async def secure_endpoint(data: dict):
    return {"processed": data}

# Expects: {"encrypted": "encrypted_data"}
# Returns: {"encrypted": "encrypted_data"}
```

## Built-in Encryption Handlers

| Name | Algorithm | Keys required | Security |
|---|---|---|---|
| `base64` | Base64 encoding | none | None — obfuscation only |
| `fernet` | Fernet (AES-128-CBC + HMAC) | `Key` | Symmetric, authenticated |
| `aes-gcm-256` | AES-256-GCM | `Key` (32 bytes) | Symmetric, authenticated |
| `chacha20-poly1305` | ChaCha20-Poly1305 | `Key` (32 bytes) | Symmetric, authenticated |
| `rsa-hybrid` | RSA-OAEP + AES-256-GCM | `PublicKey` (encrypt), `PrivateKey` (decrypt) | Asymmetric/hybrid |
| `ecdh-aes-gcm` | Ephemeral-static ECDH (P-256) + HKDF-SHA256 + AES-256-GCM | `ECPublicKey` (encrypt), `ECPrivateKey` (decrypt) | Asymmetric/hybrid, authenticated |
| `ecies` | ECIES: ECDH (P-256) + HKDF-SHA256 + AES-256-CTR + HMAC-SHA256 (encrypt-then-MAC) | `ECPublicKey` (encrypt), `ECPrivateKey` (decrypt) | Asymmetric/hybrid, authenticated |
| `hpke` | HPKE (RFC 9180) base mode: DHKEM(X25519, HKDF-SHA256) + HKDF-SHA256 + ChaCha20-Poly1305 | `HPKEPublicKey` (encrypt), `HPKEPrivateKey` (decrypt) | Asymmetric/hybrid, authenticated |

## Custom Handlers

Implement `EncryptionHandler` and register it — every decorator can then
use it by name.

```python
from typing import Any, Dict, Optional
from fastapi_payloadshield import EncryptionHandler, register_handler, PayloadShield

class MyHandler(EncryptionHandler):
    def encode(self, data: Any, config: Optional[Dict[str, Any]] = None) -> str:
        ...

    def decode(self, encoded_data: str, config: Optional[Dict[str, Any]] = None) -> Any:
        ...

register_handler("my-handler", MyHandler())

@app.post("/api/custom")
@PayloadShield.crypt("my-handler")
async def custom_endpoint(data: dict):
    return data
```

`config` is the dict returned by `PayloadShieldEnc.get_config()` — pull out
whatever keys your handler needs (`Key`, `PrivateKey`, `PublicKey`).

## How It Works

**Request decryption**: client sends `{"encrypted": "..."}` → decorator
decodes it with the configured handler → route receives the plain dict.

**Response encryption**: route returns a dict → decorator encodes it with
the configured handler → client receives `{"encrypted": "..."}`.

## Errors

| Situation | Behavior |
|---|---|
| Request decryption fails | `400` response: `{"error": "Failed to decrypt request: ..."}` |
| Unknown `encryption_type` | `ValueError` raised when the decorator is applied: `Encryption handler '<name>' not found. Available handlers: ...` |
| Missing required key (e.g. no `Key` set for `fernet`) | `ValueError` raised when encoding/decoding: `... requires 'Key' to be set via PayloadShieldEnc.init(...)` |

## Testing

```bash
# Run the example app (also prints Postman-ready request examples)
cd examples
python -m uvicorn main:app --reload

# Run the test suite
pytest
```

## Requirements

- Python 3.7+
- FastAPI 0.68+
- Starlette 0.19+
- cryptography 41+

## Project Layout

- `fastapi_payloadshield/` - Main package
  - `__init__.py` - Public exports
  - `config.py` - `PayloadShieldEnc` key configuration
  - `decorators.py` - `PayloadShield` decorators
  - `crypto.py` - Handler registry (`register_handler`, `get_handler`)
  - `EncryptionHandler.py`, `Base64EncryptionHandler.py`,
    `FernetEncryptionHandler.py`, `AESGCM256EncryptionHandler.py`,
    `ChaChaEncryptionHandler.py`, `HybridRSAEncryptionHandler.py`,
    `ECDHAESGCMEncryptionHandler.py`, `ECIESEncryptionHandler.py`,
    `HPKEEncryptionHandler.py` - Built-in handlers
- `examples/` - `main.py` demo app (routes for every handler, auto-generates
  PEM keys, prints Postman request examples on startup)
- `tests/` - pytest suite for handlers, config, and decorators
- `document/` - Additional guides (see [document/](document/))

## License

Apache-2.0 - See [LICENSE](LICENSE) for details.

---

## 📞 Support

- **GitHub Issues**: https://github.com/PayloadShield/FastAPIPS/issues
- **PyPI Page**: https://pypi.org/project/fastapi_payloadshield/
- **Author**: Ganesh Kandu <kanduganesh@gmail.com>
