Skip to main content

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 TypeField TypeExample
API KeysEncryptedTextFieldExchange read/trade/withdraw keys
API SecretsEncryptedTextFieldExchange API secrets
API PassphrasesEncryptedTextFieldExchange-specific passphrases
Private KeysEncryptedTextFieldWallet private keys
TokensEncryptedCharFieldAuthentication 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_keys if not specified
  • auto_create - Automatically set to True in development, False in 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

EnvironmentAUTO_CREATE_KEYSKey LocationBehavior
DevelopmentTrue./crypto_keys/Keys auto-generated if missing
ProductionFalseCustom pathKeys must be pre-configured
TestingTrue./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 VectorProtection
Database BreachData encrypted, useless without keys
Backup TheftBackups contain only encrypted data
SQL InjectionEven extracted data is encrypted
Log AnalysisLogs never contain plaintext secrets
Memory InspectionData 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


Need help with encryption? Contact our security team for assistance with key management, rotation, or compliance requirements.