Encryption at Rest¶
Version: 2.0 Last Updated: 2026-01-28 Status: ✅ Production Ready Architecture: Single-Tenant Rust
Overview¶
Sawabona implements encryption at rest for sensitive data, with primary focus on payment provider API keys and webhook secrets.
Architecture¶
Payment Provider API Key Encryption¶
Algorithm: AES-256-GCM Key Derivation: HKDF-SHA256 Scope: All payment provider API keys and webhook secrets
Payment provider credentials are encrypted before storage in the database:
use sawabona_core::encryption::encrypt_api_key;
let api_key = "sk_live_...";
let encrypted = encrypt_api_key(&api_key)?;
// Store encrypted value in database
Key Features:
- ✅ AES-256-GCM provides authenticated encryption
- ✅ HKDF-SHA256 derives encryption keys from master key
- ✅ Automatic encryption/decryption on read/write
- ✅ Support for key rotation without re-encryption
- ✅ Audit logging of all encryption operations
Database Encryption¶
PostgreSQL:
- Use SSL/TLS for connections
- Enable pgcrypto extension for column-level encryption if needed
- Configure TDE (Transparent Data Encryption) at OS/storage level
2. Column-Level Encryption (Primary Method)¶
Algorithm: AES-128-CBC + HMAC-SHA256 (Fernet) Scope: Sensitive customer data (primary encryption method)
Encrypted Fields:
customer_email- Customer email addressescustomer_name- Customer namesadmin_email- Admin email addressessupport_email- Support email addressesapi_key- API keyssecret_key- Secret keyswebhook_secret- Webhook secretstotp_secret- 2FA secrets
Implementation:
from sawabona.core.encrypted_column import EncryptedString
class License(Base):
customer_email: Mapped[str] = mapped_column(
EncryptedString(255),
doc="Encrypted customer email"
)
Key Management¶
Master Encryption Key¶
Environment Variable: SAWABONA_ENCRYPTION_KEY (falls back to ENCRYPTION_KEY)
Encoding: Standard base64 or URL-safe base64 (both are supported). Must decode to exactly 32 bytes.
Generation:
Storage:
- Development:
.envfile (local only) - Production: AWS Secrets Manager / Azure Key Vault / HashiCorp Vault
Key Rotation¶
Sawabona provides a comprehensive key rotation system with audit logging and progress tracking:
# Create a rotation job
from sawabona.core.key_rotation import KeyRotationService
rotation_service = KeyRotationService(db_session)
# Create new rotation job
job = rotation_service.create_rotation_job(
job_id="rotation-2025-01",
old_key_id="key-2024",
new_key_id="key-2025",
total_records=5000
)
# Start rotation
rotation_service.start_rotation("rotation-2025-01")
# Track progress
rotation_service.update_progress(
"rotation-2025-01",
processed=2500,
failed=5
)
# Get status
status = rotation_service.get_rotation_status("rotation-2025-01")
print(f"Progress: {status['progress']}%")
# Complete rotation
rotation_service.complete_rotation("rotation-2025-01")
Key Rotation Features:
- ✅ Job-based tracking with status monitoring
- ✅ Progress tracking (processed/failed records)
- ✅ Automatic audit logging of all operations
- ✅ Support for manual (OSS) and automated (SaaS) rotation
- ✅ Rollback capability for failed rotations
- ✅ Zero-downtime rotation with grace periods
Audit Logging¶
All encryption and decryption operations are automatically logged for compliance and security monitoring:
# Access audit logs
from sawabona.models.encryption_audit import EncryptionAuditLog
from sqlalchemy import select
# Query recent encryption operations
stmt = select(EncryptionAuditLog).order_by(
EncryptionAuditLog.timestamp.desc()
).limit(100)
logs = await db.execute(stmt)
for log in logs.scalars():
print(f"{log.timestamp}: {log.action} on {log.model_name}.{log.field_name}")
print(f" Record ID: {log.record_id}")
print(f" Success: {log.success}")
if log.error_message:
print(f" Error: {log.error_message}")
Audit Log Features:
- ✅ Automatic logging of all encryption/decryption operations
- ✅ SQLAlchemy event hooks for ORM tracking
- ✅ Structured logs with timestamps, action types, and error tracking
- ✅ JSON details for additional context
- ✅ Support for GDPR, HIPAA, PCI-DSS, SOC 2, ISO 27001 compliance
Configuration¶
Environment Variables¶
# Master encryption key (required for production)
SAWABONA_ENCRYPTION_KEY=<base64-encoded-key>
# Database URL
SAWABONA_DATABASE__URL=postgres://user:pass@host/db
# or
SAWABONA_DATABASE__URL=postgres:///./sawabona.db
Verification¶
# Check encryption status
from sawabona.core.encryption_at_rest import EncryptionAtRestConfig
print(f"PostgreSQL TDE: {EncryptionAtRestConfig.POSTGRES_TDE_ENABLED}")
print(f"Cipher: {EncryptionAtRestConfig.POSTGRES_CIPHER}")
print(f"Sensitive fields: {EncryptionAtRestConfig.SENSITIVE_FIELDS}")
Security Considerations¶
✅ What's Encrypted¶
- All customer PII (email, name)
- All API keys and secrets
- All webhook secrets
- All 2FA secrets
- Database connections (TLS)
⚠️ What's NOT Encrypted¶
- License keys (needed for indexing/lookup)
- Product IDs (needed for queries)
- Timestamps (needed for sorting)
- Status fields (needed for filtering)
🔐 Best Practices¶
- Rotate keys regularly (quarterly minimum)
- Store master key in HSM (production)
- Enable database backups with encryption
- Monitor access logs for suspicious queries
- Use TLS for all database connections
- Implement audit logging for sensitive data access
Performance Impact¶
- PostgreSQL pgcrypto extension: <1% overhead (extension only, no automatic encryption)
- PostgreSQL column-level encryption: 5-10% overhead (only for encrypted fields)
Compliance¶
✅ GDPR: Encryption at rest requirement ✅ HIPAA: Required for healthcare data ✅ PCI-DSS: Required for payment data ✅ SOC 2: Encryption controls ✅ ISO 27001: Data protection controls
Troubleshooting¶
Encryption Key Not Found¶
Solution: Set SAWABONA_ENCRYPTION_KEY environment variable
Decryption Failed¶
Causes:
- Wrong encryption key
- Corrupted encrypted data
- Key rotation without migration
Solution: Verify key matches encrypted data