How to Encrypt Database Data
Protect sensitive data with encryption at every level
TL;DR
TL;DR (30 minutes): Enable encryption at rest (most managed databases do this by default), use TLS for connections (add ?sslmode=require to your connection string), and encrypt sensitive fields in your application before storing them using a library like crypto or a KMS service.
Prerequisites:
- Database access (PostgreSQL, MySQL, MongoDB, etc.)
- Understanding of your data sensitivity requirements
- Node.js, Python, or similar backend language
Why This Matters
Encryption is your last line of defense. If an attacker gets access to your database files, backups, or intercepts network traffic, encryption ensures they can't read your sensitive data.
Compliance regulations like GDPR, HIPAA, and PCI-DSS require encryption of personal and payment data. Even if you're not regulated yet, encryption protects you from the catastrophic consequences of a data breach.
Understanding Encryption Layers
- Encryption at rest: Data encrypted on disk - protects against physical theft or unauthorized disk access
- Encryption in transit: Data encrypted over the network - protects against eavesdropping
- Application-level encryption: Data encrypted before storage - protects even if database is compromised
Step-by-Step Guide
Enable encryption at rest
Most managed database services enable this by default:
# Check encryption status for various platforms:
# PostgreSQL (RDS) - enabled by default with AWS KMS
# Verify in AWS Console → RDS → Database → Configuration
# MongoDB Atlas - enabled by default
# Verify in Atlas → Security → Encryption at Rest
# Supabase - enabled by default with AES-256
# No action needed
# Self-hosted PostgreSQL - enable in postgresql.conf
# Requires pgcrypto extension or disk-level encryption
For self-hosted databases, consider full-disk encryption (LUKS on Linux, BitLocker on Windows) or Transparent Data Encryption (TDE) if your database supports it.
Enable encryption in transit (TLS/SSL)
Always use encrypted connections to your database:
// PostgreSQL connection with SSL
const connectionString =
'postgresql://user:pass@host:5432/db?sslmode=require';
// MongoDB with TLS
const mongoUri =
'mongodb+srv://user:pass@cluster.mongodb.net/db?tls=true';
// MySQL with SSL
const mysqlConfig = {
host: 'your-host.com',
user: 'user',
password: 'pass',
database: 'db',
ssl: {
rejectUnauthorized: true
}
};
SSL modes for PostgreSQL:
disable- No SSL (never use in production)require- Encrypt connection, don't verify certificateverify-ca- Verify server certificate is signed by trusted CAverify-full- Verify certificate and hostname match (most secure)
Implement application-level encryption
Encrypt sensitive data before storing it:
// Node.js with built-in crypto
import crypto from 'crypto';
const ALGORITHM = 'aes-256-gcm';
const KEY = Buffer.from(process.env.ENCRYPTION_KEY, 'hex'); // 32 bytes
function encrypt(text) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(ALGORITHM, KEY, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag().toString('hex');
// Return IV + authTag + encrypted data
return iv.toString('hex') + ':' + authTag + ':' + encrypted;
}
function decrypt(encryptedData) {
const [ivHex, authTagHex, encrypted] = encryptedData.split(':');
const iv = Buffer.from(ivHex, 'hex');
const authTag = Buffer.from(authTagHex, 'hex');
const decipher = crypto.createDecipheriv(ALGORITHM, KEY, iv);
decipher.setAuthTag(authTag);
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
// Usage
const ssn = '123-45-6789';
const encryptedSSN = encrypt(ssn);
// Store encryptedSSN in database
const decryptedSSN = decrypt(encryptedSSN);
// Returns: '123-45-6789'
Use a key management service
Don't hardcode encryption keys - use a KMS:
// AWS KMS example
import { KMSClient, EncryptCommand, DecryptCommand } from "@aws-sdk/client-kms";
const kms = new KMSClient({ region: "us-east-1" });
const KEY_ID = process.env.KMS_KEY_ID;
async function encryptWithKMS(plaintext) {
const command = new EncryptCommand({
KeyId: KEY_ID,
Plaintext: Buffer.from(plaintext)
});
const response = await kms.send(command);
return Buffer.from(response.CiphertextBlob).toString('base64');
}
async function decryptWithKMS(ciphertext) {
const command = new DecryptCommand({
CiphertextBlob: Buffer.from(ciphertext, 'base64')
});
const response = await kms.send(command);
return Buffer.from(response.Plaintext).toString('utf8');
}
Encrypt specific fields in your models
Create a pattern for handling encrypted fields:
// Prisma middleware example
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
const SENSITIVE_FIELDS = ['ssn', 'taxId', 'bankAccount'];
// Encrypt on write
prisma.$use(async (params, next) => {
if (['create', 'update'].includes(params.action)) {
for (const field of SENSITIVE_FIELDS) {
if (params.args.data?.[field]) {
params.args.data[field] = encrypt(params.args.data[field]);
}
}
}
return next(params);
});
// Decrypt on read
prisma.$use(async (params, next) => {
const result = await next(params);
if (result && typeof result === 'object') {
for (const field of SENSITIVE_FIELDS) {
if (result[field]) {
result[field] = decrypt(result[field]);
}
}
}
return result;
});
Handle encryption key rotation
Plan for key rotation from the start:
// Store key version with encrypted data
function encryptWithVersion(text, keyVersion = 'v1') {
const key = getKeyByVersion(keyVersion);
const encrypted = encrypt(text, key);
return `${keyVersion}:${encrypted}`;
}
function decryptWithVersion(data) {
const [version, encrypted] = data.split(':', 1);
const key = getKeyByVersion(version);
return decrypt(encrypted, key);
}
// Re-encrypt data with new key
async function rotateEncryption(records) {
for (const record of records) {
const decrypted = decryptWithVersion(record.sensitiveField);
record.sensitiveField = encryptWithVersion(decrypted, 'v2');
await record.save();
}
}
Critical Security Considerations:
- Never store encryption keys in your database or code repository
- Use authenticated encryption (AES-GCM) to prevent tampering
- Generate keys using cryptographically secure random generators
- Encrypted fields can't be searched - plan your queries accordingly
- Back up encryption keys separately from database backups
How to Verify It Worked
- Check SSL connection: Verify your connection is encrypted
- Inspect stored data: Look at the raw database values
- Test decryption: Ensure data round-trips correctly
-- PostgreSQL: Check if connection is encrypted
SELECT ssl, version FROM pg_stat_ssl WHERE pid = pg_backend_pid();
-- View raw encrypted data
SELECT id, ssn FROM users LIMIT 1;
-- Should show something like: "v1:a3f2c1..."
-- MongoDB: Check TLS status
db.serverStatus().security
Common Errors & Troubleshooting
Error: "bad decrypt"
Wrong key or corrupted data. Check you're using the correct key version and the data wasn't modified.
Error: "IV length must be 16 bytes"
Your initialization vector is wrong size. Always generate a fresh 16-byte IV for each encryption.
Performance issues with encryption
Encryption is CPU-intensive. Consider encrypting only truly sensitive fields, not everything. Use caching for frequently accessed encrypted data.
Can't search encrypted fields
This is expected - encrypted data can't be searched directly. Use deterministic encryption for searchable fields (less secure) or maintain a separate search index.
Which fields should I encrypt?
Encrypt PII (SSN, government IDs), financial data (bank accounts, credit cards), health records, and any data that would cause significant harm if exposed. Don't encrypt everything - it adds complexity and performance overhead.
Should I hash or encrypt passwords?
Always hash passwords with bcrypt or argon2 - never encrypt them. Hashing is one-way; even if your database is compromised, the original passwords can't be recovered.
Is encryption at rest enough?
It protects against physical theft and some attack vectors, but not against attackers who gain database access through your application. For sensitive data, combine it with application-level encryption.
Related guides:Hash Passwords Securely · HashiCorp Vault Basics · AWS Secrets Manager