Skip to main content

Shield - Authenticated Symmetric Encryption

PyPI version License: MIT

Symmetric authenticated encryption with 256-bit keys (~128-bit post-quantum security).

Why Shield?

Shield builds on well-established symmetric primitives (SHA-256, HMAC-SHA256, PBKDF2). A 256-bit key gives 256-bit classical and ~128-bit post-quantum brute-force resistance, assuming these primitives are secure:

  • PBKDF2-SHA256 for key derivation (600,000 iterations)
  • SHA256-based stream cipher (AES-256-CTR equivalent)
  • HMAC-SHA256 for authentication

Installation

pip install shield-crypto

Quick Start

Basic Encryption

from shield import Shield

# Password-based encryption
s = Shield("my_password", "github.com")
encrypted = s.encrypt(b"secret data")
decrypted = s.decrypt(encrypted)  # b"secret data"

Pre-shared Key

from shield import quick_encrypt, quick_decrypt
import os

key = os.urandom(32)
encrypted = quick_encrypt(key, b"data")
decrypted = quick_decrypt(key, encrypted)

Post-Quantum Hybrid Key Exchange

For two parties who have never shared a secret, derive a session key over an open network that stays safe even against a future quantum computer ("harvest now, decrypt later"). It runs a classical (X25519) and a quantum-safe (ML-KEM-768) exchange and mixes both — an attacker must break both to win.

Requires the optional extra: pip install shield-crypto[pq].

from shield import Shield
from shield.pqhybrid import HybridPrivateKey, HybridPublicKey, initiate

# Recipient (Bob): generate a keypair, publish the public key anywhere.
bob = HybridPrivateKey.generate()
bob_public = bob.public_key().to_bytes()

# Sender (Alice): derive a shared key + a handshake to send.
handshake, key = initiate(HybridPublicKey.from_bytes(bob_public))
ciphertext = Shield.with_key(key).encrypt(b"hello bob")     # send handshake + ciphertext

# Recipient (Bob): recover the same key and decrypt.
bob_key = bob.accept(handshake)
Shield.with_key(bob_key).decrypt(ciphertext)                # b"hello bob"

See examples/pq_hybrid_demo.py for a narrated run.

Large File Encryption

from shield import StreamCipher

cipher = StreamCipher.from_password("password", b"salt")
cipher.encrypt_file("large.bin", "large.bin.enc")
cipher.decrypt_file("large.bin.enc", "large.bin.dec")

Forward Secrecy (Ratchet)

from shield import RatchetSession
import os

root_key = os.urandom(32)  # Exchanged via secure channel

alice = RatchetSession(root_key, is_initiator=True)
bob = RatchetSession(root_key, is_initiator=False)

# Each message uses a new key
encrypted = alice.encrypt(b"Hello!")
decrypted = bob.decrypt(encrypted)  # b"Hello!"

TOTP (2FA)

from shield import TOTP

# Setup
secret = TOTP.generate_secret()
totp = TOTP(secret)

# Get QR code URI for authenticator apps
uri = totp.provisioning_uri("user@example.com", "MyApp")

# Generate/verify codes
code = totp.generate()
is_valid = totp.verify(code)  # True

Web Framework Integrations

FastAPI

from fastapi import FastAPI, Depends
from shield.integrations import ShieldMiddleware, ShieldTokenAuth

app = FastAPI()

# Encrypt all JSON responses automatically
app.add_middleware(ShieldMiddleware, password="secret", service="api.example.com")

# Token-based authentication
auth = ShieldTokenAuth(password="secret", service="api.example.com")

@app.post("/login")
async def login(username: str, password: str):
    # Verify credentials...
    token = auth.create_token(user_id=username, roles=["user"])
    return {"token": token}

@app.get("/protected")
async def protected(user: dict = Depends(auth)):
    return {"user_id": user["sub"], "roles": user["roles"]}

Flask

from flask import Flask
from shield.integrations import ShieldFlask, shield_required

app = Flask(__name__)
shield = ShieldFlask(app, password="secret", service="api.example.com")

@app.route("/protected")
@shield_required(password="secret", service="api.example.com")
def protected():
    from flask import g
    return {"user_id": g.shield_user["sub"]}

Rate Limiting

from shield.integrations import RateLimiter, APIProtector

# Simple rate limiter
limiter = RateLimiter(password="secret", service="api", max_requests=100, window=60)

if limiter.is_allowed(user_id):
    process_request()
else:
    return "Rate limit exceeded", 429

# Full API protection
protector = APIProtector(password="secret", service="api")
protector.add_rate_limit(max_requests=100, window=60)
protector.add_ip_blacklist(["1.2.3.0/24"])

result = protector.check_request(client_ip=request.remote_addr, user_id=user_id)
if not result.allowed:
    return {"error": result.reason}, 403

Encrypted Cookies

from shield.integrations import EncryptedCookie

cookie = EncryptedCookie(password="secret", service="api.example.com")

# Encode session data
session_value = cookie.encode({"user_id": "123", "role": "admin"})

# Set cookie header
header = cookie.make_header("session", {"user_id": "123"})
# "session=...; Secure; HttpOnly; SameSite=Strict"

# Decode from request
data = cookie.decode(request.cookies.get("session"))

CLI Usage

# Encrypt a file
shield encrypt secret.txt -o secret.enc

# Decrypt a file
shield decrypt secret.enc -o secret.txt

# Generate random key
shield keygen

# Setup TOTP
shield totp-setup --account user@example.com

# Generate TOTP code
shield totp-code JBSWY3DPEHPK3PXP

API Reference

Shield

Main encryption class with password-derived keys.

Shield(password: str, service: str, salt: bytes = None, iterations: int = 600_000)
Shield.with_key(key: bytes)  # Create from raw 32-byte key
.encrypt(plaintext: bytes) -> bytes
.decrypt(ciphertext: bytes) -> Optional[bytes]

StreamCipher

Streaming encryption for large files.

StreamCipher(key: bytes, chunk_size: int = 65536)
StreamCipher.from_password(password: str, salt: bytes)
.encrypt_file(in_path: str, out_path: str)
.decrypt_file(in_path: str, out_path: str)
.encrypt(data: bytes) -> bytes
.decrypt(data: bytes) -> bytes

RatchetSession

Forward secrecy with key ratcheting.

RatchetSession(root_key: bytes, is_initiator: bool)
.encrypt(plaintext: bytes) -> bytes
.decrypt(ciphertext: bytes) -> Optional[bytes]

TOTP

Time-based One-Time Passwords (RFC 6238).

TOTP(secret: bytes, digits: int = 6, interval: int = 30, algorithm: str = "sha1")
TOTP.generate_secret() -> bytes
TOTP.secret_to_base32(secret: bytes) -> str
TOTP.secret_from_base32(b32: str) -> bytes
.generate(timestamp: int = None) -> str
.verify(code: str, timestamp: int = None, window: int = 1) -> bool
.provisioning_uri(account: str, issuer: str = "Shield") -> str

Security Model

Shield builds on well-established symmetric primitives. Like all practical ciphers, their security is conjectural (it relies on standard assumptions), not unconditional:

  • Symmetric encryption (AES-256 equivalent)
  • Hash functions (SHA-256)
  • HMAC authentication
  • Key derivation (PBKDF2)

Brute-forcing a full 256-bit key requires 2^256 operations; this relies on the standard assumption that SHA-256/HMAC have no exploitable structure (an assumption, not a mathematical proof).

License

MIT License - Use freely.

See Also

Metadata

Release files for shield-crypto 2.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for shield-crypto 2.3.0
File Size Uploaded Provenance Actions
shield_crypto-2.3.0.tar.gz 98.0 kB Without provenance Details for shield_crypto-2.3.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for shield-crypto 2.3.0
File Interpreter ABI Platform Actions
shield_crypto-2.3.0-py3-none-any.whl Python 3 none any Details for shield_crypto-2.3.0-py3-none-any.whl

Total release size: 219.5 kB

Release files / shield_crypto-2.3.0.tar.gz

Download URL shield_crypto-2.3.0.tar.gz
Size 98.0 kB
Tags Source
SHA-256 checksum
How to use checksums
87f1e69f7da16e138da9fc5f96b86c85ac24abf275672066adb1a33c6564515c
BLAKE2b-256 checksum
How to use checksums
187b31d7c746f8d0648f092b76f3d6b50f2962dc1ce668c46d2ba3143460d404
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.15

Release files / shield_crypto-2.3.0-py3-none-any.whl

Download URL shield_crypto-2.3.0-py3-none-any.whl
Size 121.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3a24d958de536d5e1e71be34546d9268a10df074e060c69dbd8243003ff2d1cc
BLAKE2b-256 checksum
How to use checksums
317c1789c2f8c56d315837e88d2aed06d0b0ae996bbf6fad4483acfbbd9421be
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.15

Security for shield-crypto 2.3.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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A 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.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

2.3.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page