Field-Level Encryption
Technical documentation for our field-level encryption implementation using django-crypto-fields.
🔐 Overview
We use django-crypto-fields for transparent field-level encryption of sensitive data in our database. This ensures that even if the database is compromised, sensitive information remains protected.
Why Field-Level Encryption?
Unlike full database encryption, field-level encryption provides:
- Granular Control - Only sensitive fields are encrypted
- Backup Safety - Database backups contain encrypted data
- Log Protection - Sensitive data never appears in plaintext logs
- Selective Decryption - Data decrypted only when needed
- Compliance - Meets GDPR, PCI DSS, and other regulatory requirements
📦 Implementation
Encrypted Data Types
The following sensitive data types are encrypted at the database level:
| Data Type | Field Type | Example |
|---|---|---|
| API Keys | EncryptedTextField | Exchange read/trade/withdraw keys |
| API Secrets | EncryptedTextField | Exchange API secrets |
| API Passphrases | EncryptedTextField | Exchange-specific passphrases |
| Private Keys | EncryptedTextField | Wallet private keys |
| Tokens | EncryptedCharField | Authentication and access tokens |
Configuration (django-cfg)
We use django-cfg for automatic configuration:
from django_cfg import DjangoConfig, CryptoFieldsConfig
class Config(DjangoConfig):
crypto_fields: CryptoFieldsConfig = CryptoFieldsConfig(
enabled=True,
key_path=None, # Auto: {BASE_DIR}/crypto_keys
key_prefix="", # Optional: prefix for key files
auto_create=None, # Auto: True in dev, False in prod
)
Auto-Configuration Features:
key_path- Defaults to{BASE_DIR}/crypto_keysif not specifiedauto_create- Automatically set toTruein development,Falsein production- Environment-aware defaults reduce configuration boilerplate
Usage Example
Here's how encrypted fields are used in our StockAccount model:
from django.db import models
from django_crypto_fields.fields import EncryptedTextField, EncryptedCharField
class StockAccount(models.Model):
"""Stock exchange account with encrypted credentials."""
# Exchange info
exchange = models.CharField(max_length=50)
account_name = models.CharField(max_length=100)
# Encrypted API credentials (read-only)
api_key_read = EncryptedTextField(
'API key (read)',
blank=True,
null=True,
help_text='Read-only API key (encrypted)'
)
api_secret_read = EncryptedTextField(
'API secret (read)',
blank=True,
null=True,
help_text='Read-only API secret (encrypted)'
)
# Encrypted API credentials (trade)
api_key_trade = EncryptedTextField(
'API key (trade)',
blank=True,
null=True,
help_text='Trade API key (encrypted)'
)
api_secret_trade = EncryptedTextField(
'API secret (trade)',
blank=True,
null=True,
help_text='Trade API secret (encrypted)'
)
# Encrypted passphrase (some exchanges require this)
api_passphrase = EncryptedTextField(
'API passphrase',
blank=True,
null=True,
help_text='API passphrase (encrypted)'
)
Transparent Operations:
# Writing - encryption happens automatically
account = StockAccount.objects.create(
exchange='binance',
account_name='My Trading Account',
api_key_read='your-api-key-here', # Stored encrypted
api_secret_read='your-secret-here', # Stored encrypted
)
# Reading - decryption happens automatically
print(account.api_key_read) # Decrypted on access
🔑 Encryption Details
Algorithm & Security
- Algorithm: AES-256 (Advanced Encryption Standard)
- Mode: CBC (Cipher Block Chaining)
- Key Size: 256 bits
- Key Derivation: PBKDF2 with salt
- IV (Initialization Vector): Random per-field
Key Management
Key Storage:
- Encryption keys stored separately from the database
- Default location:
{BASE_DIR}/crypto_keys/ - Production: Keys should be stored in secure vault (AWS KMS, HashiCorp Vault, etc.)
- Development: Keys auto-generated on first run
Key Files:
crypto_keys/
├── user.salt # Salt for key derivation
├── user.key # Master encryption key
└── .gitignore # Ensure keys never committed
Key Rotation:
# django-crypto-fields supports key rotation
python manage.py rotate_keys
Environment-Specific Behavior
| Environment | AUTO_CREATE_KEYS | Key Location | Behavior |
|---|---|---|---|
| Development | True | ./crypto_keys/ | Keys auto-generated if missing |
| Production | False | Custom path | Keys must be pre-configured |
| Testing | True | ./crypto_keys/ | Temporary keys for tests |
🛡️ Security Benefits
Protection Layers
✅ Database Compromise - Encrypted data unreadable without keys ✅ Backup Security - Backups contain encrypted data only ✅ Log Safety - Plaintext secrets never written to logs ✅ SQL Injection - Even if SQL injection occurs, data is encrypted ✅ Memory Dumps - Data encrypted at rest, decrypted only on access ✅ Compliance - Meets GDPR, PCI DSS, HIPAA requirements
Attack Mitigation
| Attack Vector | Protection |
|---|---|
| Database Breach | Data encrypted, useless without keys |
| Backup Theft | Backups contain only encrypted data |
| SQL Injection | Even extracted data is encrypted |
| Log Analysis | Logs never contain plaintext secrets |
| Memory Inspection | Data decrypted only when accessed |
📋 Best Practices
Development
# 1. Let django-cfg auto-create keys in development
export DJANGO_ENV=development
python manage.py migrate
# 2. Keys created automatically at ./crypto_keys/
ls crypto_keys/
# user.key user.salt
# 3. Add to .gitignore
echo "crypto_keys/" >> .gitignore
Production
# 1. Generate keys BEFORE deployment
python manage.py generate_keys --path /secure/location/
# 2. Store keys in secure vault
aws secretsmanager create-secret \
--name django-crypto-keys \
--secret-binary fileb://crypto_keys/user.key
# 3. Mount keys at runtime
export DJANGO_CRYPTO_FIELDS_KEY_PATH=/run/secrets/crypto_keys
# 4. NEVER commit keys to version control
Key Rotation
# 1. Backup database before rotation
pg_dump mydb > backup_before_rotation.sql
# 2. Rotate keys
python manage.py rotate_keys
# 3. Verify all encrypted fields
python manage.py check_encrypted_fields
# 4. Backup new keys securely
🔧 Troubleshooting
Common Issues
Issue: "Encryption keys not found"
# Solution: Generate keys
python manage.py generate_keys
Issue: "Cannot decrypt field"
# Cause: Wrong keys or corrupted data
# Solution: Check key path configuration
echo $DJANGO_CRYPTO_FIELDS_KEY_PATH
# Verify keys exist
ls -la $DJANGO_CRYPTO_FIELDS_KEY_PATH
Issue: "Performance degradation"
# Cause: Too many encrypted fields being decrypted
# Solution: Use .only() to fetch specific fields
accounts = StockAccount.objects.only('exchange', 'account_name')
# Or defer encrypted fields when not needed
accounts = StockAccount.objects.defer('api_secret_read', 'api_secret_trade')
Performance Optimization
# ❌ Bad: Decrypts all fields for all accounts
for account in StockAccount.objects.all():
print(account.exchange) # Still decrypts all encrypted fields
# ✅ Good: Only fetch needed fields
for account in StockAccount.objects.only('exchange', 'account_name'):
print(account.exchange) # No decryption overhead
# ✅ Good: Defer encryption-heavy fields
accounts = StockAccount.objects.defer('api_secret_read', 'api_secret_trade')
📚 Additional Resources
Documentation
- django-crypto-fields: GitHub Repository
- django-cfg: Configuration Guide
- AES-256: NIST Specification
Related Topics
- Security Overview - Overall security architecture
- API Authentication - API key management
- Privacy Policy - Data protection policies
Need help with encryption? Contact our security team for assistance with key management, rotation, or compliance requirements.