9.9 KiB
Database Encryption Setup Guide
Security Hardening: Database Compromise Mitigation
This guide covers the implementation of database encryption at multiple levels to protect sensitive data (PII) like phone numbers.
✅ IMPLEMENTED: Field-Level Encryption
What's Implemented
-
Field-Level Encryption for Phone Numbers
- Phone numbers are encrypted using AES-256-GCM before storing in database
- Automatic decryption when reading from database
- Backward compatibility with existing plaintext data
-
Database Access Logging
- All database queries are logged (configurable)
- Logs include: query type, tables accessed, user context, IP address, timestamp
- Sensitive parameters are redacted in logs
Configuration
1. Enable Field-Level Encryption
Add to your .env file:
# Enable field-level encryption
ENCRYPTION_ENABLED=true
# Generate encryption key (32 bytes, base64 encoded)
# Run: node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
ENCRYPTION_KEY=<your-32-byte-base64-key>
Generate Encryption Key:
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
Example output:
K8mN3pQ9rT5vW7xY1zA2bC4dE6fG8hI0jK2lM4nO6pQ8rS0tU2vW4xY6zA=
⚠️ IMPORTANT:
- Store encryption key in secrets manager (AWS Secrets Manager, HashiCorp Vault, etc.)
- Never commit encryption keys to version control
- Rotate keys periodically (requires data re-encryption)
2. Enable Database Access Logging
Add to your .env file:
# Enable database access logging
DB_ACCESS_LOGGING_ENABLED=true
# Log level: 'all' (all queries) or 'sensitive' (only sensitive tables)
DB_ACCESS_LOG_LEVEL=sensitive
Sensitive Tables (always logged if enabled):
usersotp_codesotp_requestsrefresh_tokensauth_audituser_devices
How It Works
Encryption Flow:
- Application receives plaintext phone number
- Phone number is encrypted using AES-256-GCM
- Encrypted value is stored in database
- When reading, encrypted value is automatically decrypted
Backward Compatibility:
- Existing plaintext phone numbers continue to work
- System searches for both encrypted and plaintext values
- New records are stored encrypted
- Old records can be migrated gradually
Database Access Logging:
- All queries to sensitive tables are logged
- Logs include sanitized parameters (no sensitive data)
- User context (user ID, IP, user agent) is captured
- Query duration is tracked
Code Usage
Encrypt Phone Number:
const { encryptPhoneNumber } = require('./utils/fieldEncryption');
const encryptedPhone = encryptPhoneNumber('+919876543210');
// Store encryptedPhone in database
Decrypt Phone Number:
const { decryptPhoneNumber } = require('./utils/fieldEncryption');
const plaintextPhone = decryptPhoneNumber(encryptedPhoneFromDb);
// Use plaintextPhone in application
Database Query with Context:
const db = require('./db');
// Create context from request
const context = db.createContextFromRequest(req);
// Query with context (for logging)
const result = await db.query(
'SELECT * FROM users WHERE id = $1',
[userId],
context
);
⚠️ REQUIRED: Database-Level Encryption (TDE)
What's Needed
Transparent Data Encryption (TDE) must be configured at the database server level. This is infrastructure-level encryption that protects data at rest.
PostgreSQL TDE Setup
PostgreSQL doesn't have built-in TDE, but you can use:
Option 1: PostgreSQL with pgcrypto Extension (Application-Level)
Already implemented via field-level encryption (see above).
Option 2: Database-Level Encryption (Infrastructure)
For PostgreSQL on Cloud Providers:
-
AWS RDS PostgreSQL:
- Enable encryption at rest when creating RDS instance
- Uses AWS KMS for key management
- Encryption is transparent to application
-
Google Cloud SQL:
- Enable encryption at rest in instance settings
- Uses Google Cloud KMS
-
Azure Database for PostgreSQL:
- Enable Transparent Data Encryption (TDE)
- Uses Azure Key Vault
-
Self-Hosted PostgreSQL:
- Use encrypted filesystem (LUKS, BitLocker)
- Use PostgreSQL with encryption at filesystem level
Setup Instructions
AWS RDS PostgreSQL:
-
Create Encrypted RDS Instance:
aws rds create-db-instance \ --db-instance-identifier farm-auth-db \ --db-instance-class db.t3.micro \ --engine postgres \ --master-username postgres \ --master-user-password <password> \ --allocated-storage 20 \ --storage-encrypted \ --kms-key-id <kms-key-id> -
Or Enable Encryption on Existing Instance:
- Create snapshot
- Restore snapshot with encryption enabled
- Update application connection string
Google Cloud SQL:
- Enable Encryption:
gcloud sql instances create farm-auth-db \ --database-version=POSTGRES_14 \ --tier=db-f1-micro \ --storage-type=SSD \ --disk-size=20GB \ --disk-encryption-key=<kms-key>
Azure Database for PostgreSQL:
- Enable TDE:
- Navigate to Azure Portal
- Select your PostgreSQL server
- Go to "Data encryption"
- Enable "Data encryption"
- Select Key Vault key
📋 Migration Plan
Phase 1: Enable Field-Level Encryption (Current)
✅ Status: Implemented
- Set
ENCRYPTION_ENABLED=true - Set
ENCRYPTION_KEY(from secrets manager) - New records are automatically encrypted
- Existing plaintext records continue to work (backward compatibility)
Phase 2: Migrate Existing Data
Script to migrate existing plaintext phone numbers:
-- WARNING: This requires application-level decryption/encryption
-- Run migration script that:
-- 1. Reads plaintext phone numbers
-- 2. Encrypts them using application encryption
-- 3. Updates database with encrypted values
-- Example (run via Node.js script, not direct SQL):
-- const { encryptPhoneNumber } = require('./utils/fieldEncryption');
-- const users = await db.query('SELECT id, phone_number FROM users WHERE phone_number NOT LIKE \'%:%\'');
-- for (const user of users.rows) {
-- const encrypted = encryptPhoneNumber(user.phone_number);
-- await db.query('UPDATE users SET phone_number = $1 WHERE id = $2', [encrypted, user.id]);
-- }
Phase 3: Enable Database-Level Encryption (TDE)
-
For Cloud Providers:
- Enable encryption at rest in database settings
- No application changes needed (transparent)
-
For Self-Hosted:
- Set up encrypted filesystem
- Migrate database to encrypted volume
- Update backup procedures
🔒 Security Best Practices
1. Key Management
- ✅ Store encryption keys in secrets manager (AWS Secrets Manager, HashiCorp Vault)
- ✅ Rotate keys periodically (every 90 days recommended)
- ✅ Use separate keys for different environments (dev, staging, prod)
- ✅ Never commit keys to version control
2. Access Control
- ✅ Use least privilege for database users
- ✅ Enable database access logging (
DB_ACCESS_LOGGING_ENABLED=true) - ✅ Review access logs regularly
- ✅ Set up alerts for suspicious access patterns
3. Backup Encryption
- ✅ Encrypt database backups
- ✅ Store backup encryption keys separately
- ✅ Test backup restoration procedures
4. Monitoring
- ✅ Monitor database access logs
- ✅ Set up alerts for:
- Unusual access patterns
- Failed authentication attempts
- Large data exports
- Access from unexpected IPs
📊 Compliance
GDPR Compliance
- ✅ Personal data (phone numbers) is encrypted at rest
- ✅ Access to personal data is logged
- ✅ Encryption keys are managed securely
PCI DSS Compliance
- ✅ Sensitive data is encrypted
- ✅ Access controls are in place
- ✅ Audit logging is enabled
🚨 Troubleshooting
Issue: Encryption Not Working
Symptoms:
- Phone numbers stored as plaintext
- Decryption errors
Solutions:
- Check
ENCRYPTION_ENABLED=truein environment - Verify
ENCRYPTION_KEYis set and valid (32 bytes, base64) - Check application logs for encryption errors
Issue: Backward Compatibility Broken
Symptoms:
- Existing users can't log in
- Phone number lookups fail
Solutions:
- Ensure queries search for both encrypted and plaintext
- Check that
decryptPhoneNumberhandles plaintext gracefully - Verify migration script completed successfully
Issue: Database Access Logging Not Working
Symptoms:
- No entries in
db_access_logtable
Solutions:
- Check
DB_ACCESS_LOGGING_ENABLED=true - Verify
db_access_logtable exists (auto-created on first log) - Check application logs for logging errors
📝 Checklist
Before Production:
- Generate encryption key and store in secrets manager
- Set
ENCRYPTION_ENABLED=truein production environment - Set
DB_ACCESS_LOGGING_ENABLED=truein production - Enable database-level encryption (TDE) at infrastructure level
- Test encryption/decryption with sample data
- Verify backward compatibility with existing data
- Set up monitoring for database access logs
- Document key rotation procedure
- Test backup and restore procedures
- Review and update access control policies
🔗 Related Documentation
SECURITY_AUDIT_REPORT.md- Security audit findingssrc/utils/fieldEncryption.js- Encryption implementationsrc/middleware/dbAccessLogger.js- Database access loggingsrc/db.js- Database wrapper with logging support
📞 Support
For questions or issues:
- Check application logs for encryption/decryption errors
- Review database access logs for suspicious activity
- Verify environment variables are set correctly
- Test with sample data before production deployment