Version: 1.1 Date: January 2026 Classification: Public Intended Audience: Security auditors, cryptographic assessors, compliance teams
This document provides complete technical transparency for cryptographic assessments and security audits. It contains all implementation details, algorithm specifications, and security considerations needed to evaluate CryptoServe's cryptographic architecture.
- Executive Summary
- Architecture Overview
- Cryptographic Primitives
- Key Management
- Post-Quantum Cryptography
- Authentication and Authorization
- Protocol Design
- Security Model and Threat Analysis
- Implementation Security
- Compliance and Standards
- Known Limitations
- Security Considerations for Researchers
- SDK Security
- Community Dashboard Security
CryptoServe is a cryptography-as-a-service platform providing symmetric encryption, asymmetric encryption, post-quantum cryptography, and key management capabilities via a REST API. This document provides complete transparency into the cryptographic design decisions, implementation details, and security considerations.
- Defense in Depth: Multiple layers of security controls
- Fail Secure: Errors default to denying access
- Least Privilege: Identities are scoped to specific contexts
- Cryptographic Agility: Support algorithm migration without breaking changes
- Auditability: Complete audit trail of all cryptographic operations
| Library | Version | Purpose | Audit Status |
|---|---|---|---|
cryptography |
≥42.0.0 | Core primitives (OpenSSL bindings) | Audited by Trail of Bits |
liboqs-python |
≥0.10.0 | NIST PQC (ML-KEM, ML-DSA) | NIST-validated algorithms |
PyJWT |
≥2.8.0 | JWT operations | Widely audited |
argon2-cffi |
- | Password hashing | PHC winner |
┌─────────────────────────────────────────────────────────────────┐
│ API Layer │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ /encrypt │ │ /decrypt │ │ /sign │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Authentication Layer │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Ed25519 JWT Verification │ OAuth 2.0/OIDC Providers │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Policy Engine │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Context Authorization │ Algorithm Policy Enforcement │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Crypto Engine │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Symmetric │ │ Asymmetric │ │ Hybrid PQC │ │
│ │ AES-GCM │ │ X25519 │ │ ML-KEM-768 │ │
│ │ ChaCha20 │ │ ECIES │ │ ML-DSA-65 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Key Management │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Local HKDF Derivation │ KMS Integration (AWS/GCP) │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
- Each tenant has isolated encryption contexts
- Keys are derived per-tenant using HKDF with tenant-specific info
- Database queries always include tenant_id filter
- Cross-tenant access is cryptographically impossible (different derived keys)
Algorithm: AES-256-GCM (NIST SP 800-38D)
Key Size: 256 bits
Nonce: 96 bits (12 bytes), randomly generated per encryption
Auth Tag: 128 bits
Max Message: 64 GiB (per NIST recommendation)
Implementation Notes:
- Uses
cryptography.hazmat.primitives.ciphers.aead.AESGCM - Nonce generated via
os.urandom(12)- CSPRNG backed by OS entropy - Key commitment computed and stored to prevent multi-key attacks
Code Path: app/core/crypto_engine.py:CipherFactory.encrypt_gcm()
Algorithm: ChaCha20-Poly1305 (RFC 8439)
Key Size: 256 bits
Nonce: 96 bits (12 bytes)
Auth Tag: 128 bits (Poly1305)
Use Case: Systems without AES-NI hardware acceleration
Note: Not FIPS-approved. Blocked when FIPS mode is enabled.
Algorithm: AES-CBC + HMAC-SHA256 (Encrypt-then-MAC)
Key Size: 256 bits (encryption) + 256 bits (MAC)
IV: 128 bits, randomly generated
Padding: PKCS#7
MAC: HMAC-SHA256 over (IV || ciphertext)
Security Properties:
- Separate keys derived via HKDF for encryption and authentication
- Encrypt-then-MAC construction (authenticated before decrypted)
- Constant-time MAC comparison via
hmac.compare_digest()
Code Path: app/core/crypto_engine.py:CipherFactory.encrypt_cbc()
To prevent multi-key/partitioning attacks on AES-GCM (the "Invisible Salamanders" attack class), we implement key commitment:
def compute_key_commitment(key: bytes) -> bytes:
"""HMAC-SHA256(key, "key-commitment-v1")"""
return hmac.new(key, b"key-commitment-v1", hashlib.sha256).digest()The 32-byte commitment is stored in the ciphertext header and verified during decryption. If an attacker attempts to decrypt with a different key that happens to produce valid plaintext, the commitment will not match.
Reference: Albertini et al., "How to Abuse and Fix Authenticated Encryption Without Key Commitment" (2020)
Self-describing format enables algorithm agility:
┌─────────────────────────────────────────────────────────────┐
│ Header Length (2 bytes, big-endian) │
├─────────────────────────────────────────────────────────────┤
│ Header (JSON) │
│ { │
│ "v": 3, // Format version │
│ "ctx": "user-pii", // Context name │
│ "kid": "key_user-pii_a1b2",// Key identifier │
│ "alg": "AES-256-GCM", // Algorithm │
│ "mode": "gcm", // Cipher mode │
│ "nonce": "base64...", // 12-byte nonce │
│ "kc": "base64...", // Key commitment (32 bytes) │
│ "aad": false // AAD flag │
│ } │
├─────────────────────────────────────────────────────────────┤
│ Ciphertext + Auth Tag │
└─────────────────────────────────────────────────────────────┘
Backward Compatibility: Versions 1, 2, and 3 are all supported for decryption.
All nonces are generated using os.urandom():
nonce = os.urandom(12) # For GCM, CCM, ChaCha20
iv = os.urandom(16) # For CBC, CTREntropy Source:
- Linux:
/dev/urandom(getrandom syscall when available) - macOS:
getentropy()syscall - Windows:
CryptGenRandom()
Collision Analysis:
- 96-bit nonce space: 2^96 possible values
- Birthday bound: ~2^48 encryptions before 50% collision probability
- Rate limiting and key rotation ensure we stay well below this
Master Key (KEK)
│
│ [HKDF-SHA256]
│ salt = "crypto-serve-v1"
│ info = "{context}:{version}:{key_size}"
│
▼
Data Encryption Keys (DEKs) per context
def derive_key(context: str, version: int, key_size: int) -> bytes:
hkdf = HKDF(
algorithm=hashes.SHA256(),
length=key_size,
salt=b"crypto-serve-v1",
info=f"{context}:{version}:{key_size}".encode(),
)
return hkdf.derive(master_key)Security Note: Deterministic derivation allows consistent keys across restarts without storing DEKs. The security of all DEKs depends entirely on the master key secrecy.
For production deployments, the platform supports external KMS:
| Provider | Status | Envelope Encryption |
|---|---|---|
| AWS KMS | Supported | Yes |
| GCP KMS | Planned | Yes |
| Azure Key Vault | Planned | Yes |
| HashiCorp Vault | Planned | Yes |
In KMS mode:
- Master key never leaves the HSM
- DEKs are generated by KMS and encrypted (envelope encryption)
- Encrypted DEKs can be stored with ciphertext for per-message keys
async def rotate_key(context: str, tenant_id: str) -> tuple[bytes, str]:
# 1. Mark current key as ROTATED
current_key.status = KeyStatus.ROTATED
# 2. Create new key with incremented version
new_key = Key(
version=current_version + 1,
status=KeyStatus.ACTIVE
)
# 3. Derive new key material
return derive_key(context, new_version, key_size)Important: Key rotation does NOT re-encrypt existing data. Old keys remain available (status=ROTATED) for decryption. Re-encryption must be performed separately if required.
Post-quantum keys cannot be deterministically derived and must be stored:
# Storage: Private key encrypted with AES-256-GCM
nonce = secrets.token_bytes(12)
aesgcm = AESGCM(encryption_key)
encrypted_private_key = aesgcm.encrypt(
nonce,
private_key,
associated_data=f"{context}:{key_id}:{algorithm}".encode()
)The encryption key is derived from the context's master key, binding the PQC key to its context.
CryptoServe uses liboqs (Open Quantum Safe) for post-quantum algorithms:
import oqs
kem = oqs.KeyEncapsulation("ML-KEM-768")liboqs provides production-ready implementations of NIST-standardized algorithms.
| Variant | Security Level | Public Key | Ciphertext | Shared Secret |
|---|---|---|---|---|
| ML-KEM-512 | NIST Level 1 (128-bit) | 800 bytes | 768 bytes | 32 bytes |
| ML-KEM-768 | NIST Level 3 (192-bit) | 1,184 bytes | 1,088 bytes | 32 bytes |
| ML-KEM-1024 | NIST Level 5 (256-bit) | 1,568 bytes | 1,568 bytes | 32 bytes |
| Variant | Security Level | Public Key | Signature |
|---|---|---|---|
| ML-DSA-44 | NIST Level 2 (~128-bit) | 1,312 bytes | 2,420 bytes |
| ML-DSA-65 | NIST Level 3 (~192-bit) | 1,952 bytes | 3,309 bytes |
| ML-DSA-87 | NIST Level 5 (~256-bit) | 2,592 bytes | 4,627 bytes |
We implement hybrid encryption combining classical and post-quantum algorithms:
Hybrid Mode: AES-256-GCM + ML-KEM-768 (Recommended)
Encryption:
1. KEM Encapsulation: (kem_ciphertext, shared_secret) = ML-KEM.Encap(public_key)
2. Key Derivation: symmetric_key = HKDF-SHA256(shared_secret, info="cryptoserve-hybrid-v1")
3. AEAD Encryption: ciphertext = AES-GCM.Encrypt(symmetric_key, plaintext, nonce)
4. Output: kem_ciphertext || ciphertext
Decryption:
1. KEM Decapsulation: shared_secret = ML-KEM.Decap(kem_ciphertext, private_key)
2. Key Derivation: symmetric_key = HKDF-SHA256(shared_secret, info="cryptoserve-hybrid-v1")
3. AEAD Decryption: plaintext = AES-GCM.Decrypt(symmetric_key, ciphertext, nonce)
Security Rationale: Hybrid mode provides security if EITHER algorithm remains secure. This is the recommended approach during the PQC transition period per NIST guidance.
┌─────────────────────────────────────────────────────────────┐
│ Header Length (2 bytes) │
├─────────────────────────────────────────────────────────────┤
│ Header (JSON) │
│ { │
│ "v": 1, │
│ "mode": "AES-256-GCM+ML-KEM-768", │
│ "kid": "pqc_key_abc123", │
│ "nonce": "base64...", │
│ "kem_ct_len": 1088 │
│ } │
├─────────────────────────────────────────────────────────────┤
│ KEM Ciphertext (1088 bytes for ML-KEM-768) │
├─────────────────────────────────────────────────────────────┤
│ AEAD Ciphertext + Auth Tag │
└─────────────────────────────────────────────────────────────┘
CryptoServe uses a dual-token system:
Algorithm: EdDSA (Ed25519)
Lifetime: 1 hour
Signing Key: Per-application Ed25519 private key
Audience: "cryptoserve-api"
JWT Claims:
{
"iss": "cryptoserve",
"sub": "app_backend_abc123",
"aud": "cryptoserve-api",
"iat": 1704067200,
"exp": 1704070800,
"type": "access",
"name": "Backend Service",
"team": "platform",
"env": "production",
"contexts": ["user-pii", "payment-data"]
}Algorithm: HS256 (HMAC-SHA256)
Lifetime: 30 days
Signing Key: Master key
Storage: SHA-256 hash only (not the token itself)
Security Properties:
- Refresh token hash stored, not the token → Database compromise doesn't expose tokens
jticlaim enables revocation- Constant-time comparison:
secrets.compare_digest()
Each application gets a unique Ed25519 keypair:
private_key = Ed25519PrivateKey.generate() # 32 bytes entropy from CSPRNGPrivate Key Storage:
# Encrypted with Fernet (AES-128-CBC + HMAC-SHA256)
fernet_key = base64.urlsafe_b64encode(
hashlib.sha256(master_key.encode()).digest()
)
encrypted = Fernet(fernet_key).encrypt(private_key_pem)Note: This uses SHA-256 truncated to 256 bits for the Fernet key. While Fernet internally uses AES-128, the key derivation provides 256 bits of entropy.
Identities are authorized for specific encryption contexts:
# During encryption
if context_name not in identity.allowed_contexts:
raise AuthorizationError(
f"Identity not authorized for context: {context_name}"
)Supported identity providers:
- GitHub OAuth
- Google OIDC
- Azure AD
- Okta
- Generic OIDC
All OAuth flows use:
- PKCE (Proof Key for Code Exchange) where supported
- State parameter for CSRF protection
- Secure token storage
1. Client sends: POST /api/v1/crypto/encrypt
{
"plaintext": "base64...",
"context": "user-pii",
"associated_data": "base64..." (optional)
}
2. Server validates:
- JWT signature (Ed25519)
- JWT not expired
- Identity authorized for context
- Context exists and is active
3. Policy evaluation:
- Check algorithm policy enforcement
- Check custom policies (CEL expressions)
4. Key retrieval:
- Get or create key for context
- Derive key material via HKDF
5. Encryption:
- Generate random nonce (12 bytes)
- Compute key commitment
- Encrypt with AES-GCM
- Pack into self-describing format
6. Audit logging:
- Log operation, context, identity, timing
- Log algorithm details (cipher, mode, key_bits)
7. Return:
{
"ciphertext": "base64...",
"algorithm": "AES-256-GCM",
"key_id": "key_user-pii_a1b2",
"warnings": []
}
1. Client sends: POST /api/v1/crypto/decrypt
{
"ciphertext": "base64...",
"context": "user-pii",
"associated_data": "base64..." (optional, must match encryption)
}
2. Parse ciphertext header:
- Validate format version (1, 2, or 3)
- Extract: context, key_id, algorithm, mode, nonce, key_commitment
3. Validate:
- Context matches request
- Identity authorized for context
- Key exists and is not revoked
4. Key commitment verification:
- Compute expected commitment from retrieved key
- Compare with stored commitment (constant-time)
- Reject if mismatch
5. Decryption:
- Decrypt with appropriate mode
- AEAD tag verification (implicit in decrypt call)
6. Return plaintext
Important: Error messages are designed to not leak sensitive information:
# BAD: Leaks whether key exists
raise DecryptionError(f"Key {key_id} not found")
# GOOD: Generic message
raise DecryptionError("Decryption failed")However, for debugging purposes in non-production environments, detailed errors may be enabled. The ciphertext context mismatch IS reported to help developers identify configuration issues.
Trusted:
- The platform operator (has access to master key)
- The underlying infrastructure (OS, database, network)
- The cryptographic libraries (cryptography, liboqs)
Untrusted:
- End users (applications using the API)
- Network between client and server (TLS required)
- Stored data at rest (encrypted)
Impact: Complete compromise of all encrypted data
Mitigations:
- Production: Store master key in HSM (AWS KMS, etc.)
- Key rotation capability (changes derivation context)
- Audit logging of all key operations
Impact: Access to encrypted data, key metadata, audit logs
Mitigations:
- DEKs are derived, not stored (development mode)
- PQC private keys encrypted at rest
- Refresh tokens stored as SHA-256 hashes only
- Database encryption at rest (recommended)
Impact: Loss of authenticity, potential plaintext recovery
Mitigations:
- Nonces generated via
os.urandom()(96-bit random) - Key rotation after ~2^32 operations (configurable)
- Audit logging enables detection
Analysis: With 96-bit random nonces and 2^32 messages per key, collision probability is ~2^-32 (negligible).
Impact: Key recovery, authentication bypass
Mitigations:
hmac.compare_digest()for all MAC comparisonssecrets.compare_digest()for token hash comparison- Ed25519 signatures are deterministic (no timing on nonce generation)
Impact: Modified ciphertext decrypts to attacker-controlled plaintext
Mitigations:
- AEAD modes (GCM, CCM, ChaCha20-Poly1305) provide authentication
- CBC mode uses Encrypt-then-MAC construction
- Key commitment prevents partition attacks
Impact: Weaker encryption than intended
Mitigations:
- Algorithm policy enforcement per context
- Policy enforcement modes: none, warn, enforce
- Ciphertext includes algorithm in authenticated header
Impact: Decryption of data encrypted with classical algorithms
Mitigations:
- Hybrid PQC modes available (ML-KEM + AES-GCM)
- Algorithm agility allows migration
- Self-describing ciphertext format supports versioning
The following are NOT protected against:
- Compromised client application: If the application itself is compromised, it has legitimate access to decrypt data
- Side-channel attacks on the server: We rely on library implementations for side-channel resistance
- Memory forensics on running server: Keys exist in memory during operations
- Malicious platform operator: The operator can access the master key
Secure memory zeroization for sensitive data:
def secure_zero(data: bytearray) -> None:
"""Overwrite memory with zeros using ctypes.memset."""
buffer_type = ctypes.c_char * len(data)
buffer = buffer_type.from_buffer(data)
ctypes.memset(ctypes.addressof(buffer), 0, len(data))SecureBytes context manager:
with SecureBytes(key_material) as secure_key:
result = encrypt(secure_key.data, plaintext)
# Key automatically zeroed on exitLimitations:
- Python's garbage collector may leave copies in memory
- Compiler optimizations might eliminate zeroization
- This is best-effort, not guaranteed secure erasure
All security-critical comparisons use constant-time functions:
# MAC verification
if not hmac.compare_digest(expected_mac, computed_mac):
raise DecryptionError("Authentication failed")
# Token hash verification
if not secrets.compare_digest(token_hash, stored_hash):
return None- Maximum plaintext sizes enforced (64 GiB for GCM, 64 KiB for CCM)
- Context names validated against allowlist
- JWT claims validated with strict type checking
- Base64 decoding errors caught and handled
Error messages are designed to:
- Help legitimate developers debug issues
- Not leak information useful to attackers
# Decryption errors are generic
raise DecryptionError("Decryption failed")
# But context mismatch is reported (helps debugging, not security-sensitive)
raise DecryptionError(f"Context mismatch: expected {expected}, got {actual}")FIPS mode can be enabled via configuration:
FIPS_MODE=enabled # Strict enforcement
FIPS_MODE=preferred # Use FIPS if available, warn otherwise
FIPS_MODE=disabled # No restrictions (default)
FIPS-Approved Algorithms:
- Symmetric: AES (128, 192, 256) in GCM, CBC, CTR, CCM modes
- Hash: SHA-256, SHA-384, SHA-512, SHA3-*
- MAC: HMAC with approved hash functions
- KDF: HKDF, PBKDF2, KBKDF
- Asymmetric: RSA (2048+), ECDSA (P-256, P-384), EdDSA
- PQC: ML-KEM (FIPS 203), ML-DSA (FIPS 204)
Blocked in FIPS Mode:
- ChaCha20-Poly1305 (RFC, not NIST)
- AES-GCM-SIV (RFC, not NIST)
- Argon2, bcrypt (not NIST-approved)
| Standard | Description | Status |
|---|---|---|
| SP 800-38D | AES-GCM | Compliant |
| SP 800-38C | AES-CCM | Compliant |
| SP 800-38A | AES-CBC, CTR | Compliant |
| SP 800-56C | Key Derivation | Compliant (HKDF) |
| SP 800-108 | KDF in Counter Mode | Compliant (KBKDF) |
| SP 800-132 | Password-Based Key Derivation | Compliant (PBKDF2) |
| FIPS 186-4 | Digital Signatures | Compliant (ECDSA) |
| FIPS 203 | ML-KEM | Compliant |
| FIPS 204 | ML-DSA | Compliant |
| Standard | Relevance | Implementation |
|---|---|---|
| PCI-DSS | Payment data encryption | Supported via payment-data context |
| HIPAA | Health data encryption | Supported via health-records context |
| GDPR | Data protection | Encryption + audit logging |
| SOC 2 | Security controls | Audit logging, access controls |
-
No Forward Secrecy for Symmetric Encryption
- Key compromise exposes all data encrypted with that key
- Mitigation: Key rotation, per-message keys (with KMS)
-
Deterministic Key Derivation
- In development mode, DEKs can be rederived if master key is known
- Mitigation: Use KMS in production for true envelope encryption
-
Python Memory Model
- Sensitive data may persist in memory due to garbage collection
- Mitigation: SecureBytes helper (best-effort)
-
AES-GCM Nonce Size
- 96-bit nonces limit safe usage to ~2^32 messages per key
- Mitigation: Key rotation, monitoring via audit logs
-
No Key Escrow/Recovery
- Lost keys mean lost data
- Mitigation: Backup procedures, key export capabilities
-
Single Master Key
- All tenant keys derived from one master key
- Mitigation: Per-tenant KMS keys in production
-
No Hardware Security Module by Default
- Development mode uses software key storage
- Mitigation: KMS integration for production
-
Audit Log Storage
- Logs stored in same database as application data
- Mitigation: External log shipping, immutable storage
-
No Streaming Encryption
- Entire plaintext must fit in memory
- Future: Chunked encryption support
-
No Client-Side Encryption
- All encryption happens server-side
- Future: Client SDK with local encryption option
We invite security researchers to examine:
-
Nonce handling in
crypto_engine.py- Is
os.urandom()properly used? - Any possibility of nonce reuse?
- Is
-
Key derivation in
key_manager.py- Is HKDF correctly applied?
- Is the info parameter sufficient for domain separation?
-
Token management in
token_manager.py- Is the refresh token hash scheme secure?
- Any timing leaks in verification?
-
Hybrid PQC in
hybrid_crypto.py- Is the combiner (HKDF) correctly applied?
- Any issues with the serialization format?
-
CBC/CTR HMAC in
CipherFactory- Is Encrypt-then-MAC correctly implemented?
- Is the key separation sufficient?
Please report security vulnerabilities via:
- Email: security@cryptoserve.dev (if applicable)
- GitHub Security Advisories
Please include:
- Description of the vulnerability
- Steps to reproduce
- Potential impact assessment
- Suggested fix (if any)
[Details of bug bounty program if applicable]
The CryptoServe Python SDK provides a zero-configuration interface for applications to access cryptographic services. This section documents the security architecture of the SDK.
The SDK uses a two-phase authentication approach:
Phase 1: Developer Authentication (one-time)
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Developer │──────│ cryptoserve │──────│ GitHub OAuth │
│ │ │ login │ │ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │
│ ▼
│ ~/.cryptoserve/credentials.json
│ │
│ ▼
Phase 2: Application Auto-Registration
│ ┌─────────────────┐
└───────────────│ CryptoServe() │
│ constructor │
└─────────────────┘
│
▼
POST /api/v1/applications/sdk/register
│
▼
Application tokens stored in
~/.cryptoserve/apps/{app_id}.json
All credentials are stored in ~/.cryptoserve/ with restricted permissions:
~/.cryptoserve/
├── credentials.json # Session token (chmod 600)
└── apps/
└── {app_id}.json # Per-app credentials (chmod 600)
Security Properties:
- Directory created with mode 0700 (owner-only access)
- Files created with mode 0600 (owner read/write only)
- Tokens never logged or printed in full
- Session tokens are JWT with 7-day expiry
Each registered application receives:
| Token Type | Algorithm | Lifetime | Storage |
|---|---|---|---|
| Access Token | Ed25519 (EdDSA) | 1 hour | Memory only |
| Refresh Token | HS256 | 30 days | SHA-256 hash in DB |
Ed25519 Keypair:
- Generated per-application at registration
- Private key encrypted with Fernet (AES-128-CBC + HMAC)
- Public key stored in application record
- Enables JWT signature verification without master key
The SDK's auto-registration feature (CryptoServe() constructor):
crypto = CryptoServe(
app_name="my-service",
team="platform",
environment="development",
contexts=["user-pii"]
)Security Controls:
- Requires valid session token from
cryptoserve login - Application name + environment must be unique per user
- Existing apps return new tokens (idempotent)
- Contexts default to
["default"]if not specified - Rate limited to prevent abuse
The SDK implements automatic token refresh:
# Transparent to application code
if self._should_refresh(): # <5 minutes remaining
with self._refresh_lock:
self._do_refresh()Security Properties:
- Thread-safe refresh with mutex lock
- Refresh occurs before token expiry (5-minute buffer)
- Failed refresh raises
TokenRefreshError - Refresh token rotation on each use
Hash and MAC operations execute locally without server calls:
# These never leave the client
hash_hex = crypto.hash(data, algorithm="sha256")
mac_hex = crypto.mac(data, key, algorithm="hmac-sha256")Supported Algorithms:
- Hash: SHA-256, SHA-384, SHA-512, SHA3-256, BLAKE2b
- MAC: HMAC-SHA256, HMAC-SHA512
The web dashboard provides administrative access to CryptoServe. This section documents its security architecture.
Primary: GitHub OAuth 2.0
- PKCE enabled for authorization code flow
- State parameter for CSRF protection
- Tokens stored in HTTP-only, Secure cookies
Development Mode:
DEV_MODE=trueenables username/password login- Intended only for local development
- Disabled by default in production builds
Cookie: access_token=<JWT>
├── Algorithm: HS256
├── Expiry: 7 days
├── HttpOnly: true
├── Secure: true (production)
├── SameSite: Lax
└── Path: /
All database queries are scoped by tenant_id:
# Every query includes tenant filter
result = await db.execute(
select(Context)
.where(Context.tenant_id == user.tenant_id)
.where(Context.name == context_name)
)Isolation Guarantees:
- Users cannot see other tenants' applications
- Users cannot access other tenants' contexts
- Users cannot view other tenants' audit logs
- Key derivation includes tenant ID (cryptographic isolation)
| Role | Capabilities |
|---|---|
| Viewer | View applications, contexts, usage stats |
| Developer | Create/manage own applications |
| Admin | Manage all tenant resources, contexts, policies |
| Owner | Full control including billing, team management |
All dashboard API endpoints:
- Authentication Required:
get_current_userdependency - Tenant Scoping: Queries filtered by
user.tenant_id - Input Validation: Pydantic models with strict types
- Rate Limiting: Per-user and per-IP limits
- Audit Logging: All mutations logged
Operations requiring additional verification:
| Operation | Additional Control |
|---|---|
| Delete application | Confirmation required |
| Rotate tokens | Old tokens immediately invalidated |
| Change algorithm policy | Enforcement mode warning |
| Export keys | Not supported (by design) |
| View private keys | Never exposed via API |
All dashboard operations are logged:
{
"timestamp": "2026-01-02T12:00:00Z",
"user_id": "user_abc123",
"tenant_id": "tenant_xyz789",
"action": "application.create",
"resource_type": "application",
"resource_id": "app_my-service_a1b2",
"details": {
"name": "my-service",
"team": "platform",
"contexts": ["user-pii"]
},
"ip_address": "192.168.1.100",
"user_agent": "Mozilla/5.0..."
}Log Retention: 90 days by default (configurable)
The dashboard implements strict CSP headers:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
connect-src 'self' https://api.github.com;
frame-ancestors 'none';
| Algorithm | Key Size | Nonce/IV | Auth Tag | Max Message | FIPS |
|---|---|---|---|---|---|
| AES-128-GCM | 128 bits | 96 bits | 128 bits | 64 GiB | Yes |
| AES-256-GCM | 256 bits | 96 bits | 128 bits | 64 GiB | Yes |
| AES-256-CBC | 256 bits | 128 bits | HMAC-256 | Unlimited | Yes |
| AES-256-CTR | 256 bits | 96 bits | HMAC-256 | Unlimited | Yes |
| AES-256-CCM | 256 bits | 96 bits | 128 bits | 64 KiB | Yes |
| ChaCha20-Poly1305 | 256 bits | 96 bits | 128 bits | Unlimited | No |
| Algorithm | Output | Block Size | FIPS |
|---|---|---|---|
| SHA-256 | 256 bits | 512 bits | Yes |
| SHA-384 | 384 bits | 1024 bits | Yes |
| SHA-512 | 512 bits | 1024 bits | Yes |
| SHA3-256 | 256 bits | 1088 bits | Yes |
| BLAKE2b | 512 bits | 1024 bits | No |
| Algorithm | Standard | FIPS |
|---|---|---|
| HKDF-SHA256 | RFC 5869 | Yes |
| PBKDF2-SHA256 | NIST SP 800-132 | Yes |
| KBKDF-HMAC-SHA256 | NIST SP 800-108 | Yes |
| Argon2id | RFC 9106 | No |
| scrypt | RFC 7914 | No* |
*scrypt is NIST-approved but not FIPS-validated
{"v":1,"ctx":"...","kid":"...","alg":"...","nonce":"..."}{"v":2,"ctx":"...","kid":"...","alg":"...","mode":"...","nonce":"..."}{"v":3,"ctx":"...","kid":"...","alg":"...","mode":"...","nonce":"...","kc":"...","aad":false}| Library | Minimum Version | Recommended | Notes |
|---|---|---|---|
| cryptography | 42.0.0 | Latest | OpenSSL bindings |
| liboqs-python | 0.10.0 | Latest | NIST PQC |
| PyJWT | 2.8.0 | Latest | JWT handling |
| argon2-cffi | 21.0.0 | Latest | Password hashing |
Document History:
- v1.1 (January 2026): Added SDK Security and Dashboard Security sections
- v1.0 (January 2026): Initial public release
Contact:
- Technical questions: engineering@cryptoserve.dev
- Security issues: security@cryptoserve.dev