Skip to content

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 addresses
  • customer_name - Customer names
  • admin_email - Admin email addresses
  • support_email - Support email addresses
  • api_key - API keys
  • secret_key - Secret keys
  • webhook_secret - Webhook secrets
  • totp_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:

# Generate a 32-byte key in URL-safe base64
openssl rand -base64 32

Storage:

  • Development: .env file (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

  1. Rotate keys regularly (quarterly minimum)
  2. Store master key in HSM (production)
  3. Enable database backups with encryption
  4. Monitor access logs for suspicious queries
  5. Use TLS for all database connections
  6. 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

EncryptionKeyNotFoundError: Encryption key not found in environment variable

Solution: Set SAWABONA_ENCRYPTION_KEY environment variable

Decryption Failed

DecryptionError: Decryption failed

Causes:

  • Wrong encryption key
  • Corrupted encrypted data
  • Key rotation without migration

Solution: Verify key matches encrypted data

References