The Complete Guide to Cache Security and Encryption
Caches often contain sensitive data: session tokens, user information, API keys, personal data. Yet many teams treat cache security as an afterthought. An unsecured cache is a critical vulnerability: it bypasses database access controls, stores unencrypted data in memory, and often lacks authentication. This guide covers everything you need to secure cache infrastructure properly.
Common Cache Security Vulnerabilities
Top Cache Security Risks
- No authentication: Default Redis/Memcached installations allow anyone to connect
- Unencrypted transit: Data transmitted in plaintext over network
- Unencrypted at rest: Sensitive data stored in memory without encryption
- Public exposure: Cache instances accessible from internet
- Weak credentials: Simple passwords like "redis123"
- Privilege escalation: Ability to run arbitrary commands (CONFIG, EVAL)
Layer 1: Network Security
Private Network Isolation
Never expose cache instances to the public internet. Use VPCs, private subnets, and security groups.
# AWS Security Group for Redis
resource "aws_security_group" "redis" {
name = "redis-cache"
description = "Security group for Redis cache"
vpc_id = aws_vpc.main.id
# Only allow connections from application servers
ingress {
from_port = 6379
to_port = 6379
protocol = "tcp"
security_groups = [aws_security_group.app_servers.id]
}
# Block all inbound from internet
# Block all outbound except VPC
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = [aws_vpc.main.cidr_block]
}
}
Firewall Rules
# iptables rules to restrict Redis access
# Only allow connections from application servers
iptables -A INPUT -p tcp --dport 6379 -s 10.0.1.0/24 -j ACCEPT
iptables -A INPUT -p tcp --dport 6379 -j DROP
# Log unauthorized access attempts
iptables -A INPUT -p tcp --dport 6379 -j LOG --log-prefix "Redis unauthorized: "
Layer 2: Authentication
Redis Authentication
# Redis configuration (redis.conf)
# Enable authentication
requirepass "$(openssl rand -base64 32)"
# Use ACLs for fine-grained permissions (Redis 6+)
# Create read-only user
ACL SETUSER readonly on >readonly_password ~* -@all +@read
# Create read-write user for application
ACL SETUSER app on >strong_app_password ~* -@all +@read +@write +@string +@hash +@list
# Disable dangerous commands
rename-command FLUSHDB ""
rename-command FLUSHALL ""
rename-command CONFIG "CONFIG_67890_SECRET"
rename-command EVAL ""
Application Connection with Auth
// Node.js Redis client with authentication
const Redis = require('ioredis');
const redis = new Redis({
host: process.env.REDIS_HOST,
port: 6379,
password: process.env.REDIS_PASSWORD,
// Use TLS
tls: {
ca: fs.readFileSync('/path/to/ca.crt'),
cert: fs.readFileSync('/path/to/client.crt'),
key: fs.readFileSync('/path/to/client.key')
},
// Connection timeout
connectTimeout: 10000,
// Retry strategy
retryStrategy: (times) => {
if (times > 3) {
return null; // Stop retrying
}
return Math.min(times * 100, 3000);
}
});
Layer 3: Encryption in Transit
TLS/SSL Configuration
# Generate self-signed certificate (for testing)
openssl req -x509 -nodes -newkey rsa:4096 \
-keyout redis.key -out redis.crt -days 365
# Redis TLS configuration
port 0
tls-port 6379
tls-cert-file /etc/redis/certs/redis.crt
tls-key-file /etc/redis/certs/redis.key
tls-ca-cert-file /etc/redis/certs/ca.crt
# Require TLS for all connections
tls-auth-clients yes
# Disable non-TLS port entirely
port 0
Application TLS Connection
// Python Redis with TLS
import redis
import ssl
redis_client = redis.StrictRedis(
host='cache.company.com',
port=6379,
password=os.environ['REDIS_PASSWORD'],
ssl=True,
ssl_cert_reqs=ssl.CERT_REQUIRED,
ssl_ca_certs='/path/to/ca.crt',
ssl_certfile='/path/to/client.crt',
ssl_keyfile='/path/to/client.key'
)
Layer 4: Encryption at Rest
Application-Level Encryption
Encrypt sensitive data before caching:
const crypto = require('crypto');
class EncryptedCache {
constructor(cache, encryptionKey) {
this.cache = cache;
this.key = Buffer.from(encryptionKey, 'hex');
}
encrypt(data) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-256-gcm', this.key, iv);
let encrypted = cipher.update(JSON.stringify(data), 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag();
return JSON.stringify({
iv: iv.toString('hex'),
authTag: authTag.toString('hex'),
encrypted: encrypted
});
}
decrypt(encryptedData) {
const { iv, authTag, encrypted } = JSON.parse(encryptedData);
const decipher = crypto.createDecipheriv(
'aes-256-gcm',
this.key,
Buffer.from(iv, 'hex')
);
decipher.setAuthTag(Buffer.from(authTag, 'hex'));
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return JSON.parse(decrypted);
}
async get(key) {
const encrypted = await this.cache.get(key);
if (!encrypted) return null;
return this.decrypt(encrypted);
}
async set(key, value, options) {
const encrypted = this.encrypt(value);
return this.cache.set(key, encrypted, options);
}
}
// Usage
const encryptionKey = process.env.CACHE_ENCRYPTION_KEY; // 32-byte key
const secureCache = new EncryptedCache(redis, encryptionKey);
// Data is encrypted before caching
await secureCache.set('user:123', {
ssn: '123-45-6789',
creditCard: '4111111111111111'
});
Field-Level Encryption
Encrypt only sensitive fields instead of entire objects:
class FieldEncryptedCache {
constructor(cache, encryptionKey, sensitiveFields) {
this.cache = cache;
this.key = encryptionKey;
this.sensitiveFields = new Set(sensitiveFields);
}
async set(key, value) {
const encrypted = { ...value };
// Encrypt only sensitive fields
for (const field of this.sensitiveFields) {
if (encrypted[field]) {
encrypted[field] = this.encrypt(encrypted[field]);
encrypted[`${field}_encrypted`] = true;
}
}
return this.cache.set(key, encrypted);
}
async get(key) {
const data = await this.cache.get(key);
if (!data) return null;
// Decrypt sensitive fields
for (const field of this.sensitiveFields) {
if (data[`${field}_encrypted`]) {
data[field] = this.decrypt(data[field]);
delete data[`${field}_encrypted`];
}
}
return data;
}
}
// Usage
const cache = new FieldEncryptedCache(
redis,
encryptionKey,
['ssn', 'creditCard', 'password']
);
await cache.set('user:123', {
name: 'Alice', // Not encrypted
email: 'alice@example.com', // Not encrypted
ssn: '123-45-6789', // Encrypted
creditCard: '4111...' // Encrypted
});
Layer 5: Access Control
Redis ACLs (Access Control Lists)
# Define roles with specific permissions
# Read-only role (for analytics, reporting)
ACL SETUSER analytics on >analytics_pass \
~analytics:* \
-@all +@read
# Application role (read/write specific patterns)
ACL SETUSER app on >app_pass \
~user:* ~session:* ~product:* \
-@all +@read +@write +@string +@hash +@set
# Admin role (full access)
ACL SETUSER admin on >admin_pass \
~* \
+@all
# Disable default user
ACL SETUSER default off
Audit Logging
# Redis slow log for auditing
CONFIG SET slowlog-log-slower-than 10000 # Log queries >10ms
CONFIG SET slowlog-max-len 1000
# Application-level audit logging
class AuditedCache {
async get(key, context) {
const startTime = Date.now();
const value = await this.cache.get(key);
const duration = Date.now() - startTime;
await this.auditLog({
operation: 'GET',
key: key,
user: context.userId,
ip: context.ip,
duration: duration,
result: value ? 'hit' : 'miss',
timestamp: new Date().toISOString()
});
return value;
}
async auditLog(entry) {
// Log to secure audit trail
await auditDB.insert('cache_audit', entry);
// Alert on suspicious activity
if (entry.key.includes('admin') && !entry.user.isAdmin) {
await securityAlert('Unauthorized admin cache access', entry);
}
}
}
Layer 6: Data Classification and TTLs
Sensitive Data Handling
const DATA_CLASSIFICATIONS = {
public: {
ttl: 3600, // 1 hour
encrypt: false
},
internal: {
ttl: 900, // 15 minutes
encrypt: false
},
confidential: {
ttl: 300, // 5 minutes
encrypt: true
},
restricted: {
ttl: 60, // 1 minute
encrypt: true,
auditAccess: true
}
};
async function cacheData(key, value, classification) {
const policy = DATA_CLASSIFICATIONS[classification];
let dataToCache = value;
if (policy.encrypt) {
dataToCache = encrypt(value);
}
await cache.set(key, dataToCache, { ttl: policy.ttl });
if (policy.auditAccess) {
await auditLog('cache_write', { key, classification });
}
}
Security Best Practices Checklist
Infrastructure
- ☑ Cache instances in private VPC/subnet
- ☑ Security groups restrict access to application servers
- ☑ No public IP addresses on cache instances
- ☑ Firewall rules block unauthorized access
Authentication & Authorization
- ☑ Strong authentication enabled (not default passwords)
- ☑ ACLs configured with principle of least privilege
- ☑ Dangerous commands disabled (FLUSHALL, CONFIG, EVAL)
- ☑ Regular password rotation (every 90 days)
Encryption
- ☑ TLS/SSL for all connections
- ☑ Sensitive data encrypted at application level
- ☑ Encryption keys stored in secure key management (AWS KMS, Vault)
- ☑ Regular key rotation
Monitoring & Compliance
- ☑ Audit logging for sensitive data access
- ☑ Alerts for failed authentication attempts
- ☑ Regular security scans and penetration testing
- ☑ Compliance with GDPR, HIPAA, SOC2 requirements
Compliance Considerations
GDPR
- Encrypt personal data in cache
- Implement data retention policies (TTLs)
- Support right to erasure (delete user data)
- Maintain audit logs of data access
HIPAA
- Encryption in transit and at rest
- Access controls and audit logging
- Short TTLs for PHI data
- Business Associate Agreements with cache providers
PCI-DSS
- Never cache full credit card numbers
- Tokenize payment data before caching
- Encrypt cardholder data at rest
- Restrict access to cardholder data
Incident Response
Breach Detection
// Monitor for suspicious activity
async function detectSecurityAnomalies() {
// Failed authentication attempts
const failedAuths = await redis.get('failed_auth_count');
if (failedAuths > 10) {
await alertSecurity('Multiple failed Redis authentications');
}
// Unusual access patterns
const accessRate = await getAccessRate();
if (accessRate > baseline * 5) {
await alertSecurity('Unusual cache access spike detected');
}
// Access to admin keys by non-admin users
const adminAccess = await auditDB.query(`
SELECT * FROM cache_audit
WHERE key LIKE 'admin:%' AND user_role != 'admin'
AND timestamp > NOW() - INTERVAL '1 hour'
`);
if (adminAccess.length > 0) {
await alertSecurity('Unauthorized admin cache access', adminAccess);
}
}
Breach Response Runbook
- Immediate: Rotate all cache credentials
- Isolate: Block suspicious IP addresses
- Audit: Review access logs for compromised data
- Invalidate: Flush potentially compromised cache data
- Notify: Inform affected users if PII exposed
- Review: Conduct post-mortem, update security policies
Conclusion
Cache security requires defense in depth: network isolation, authentication, encryption in transit and at rest, access controls, audit logging, and compliance adherence. The most critical steps are enabling authentication, using TLS for all connections, and encrypting sensitive data before caching.
Security is not optional. A breach of your cache can expose the same sensitive data as a database breach, but with less visibility and often weaker controls. Implement these security layers systematically, starting with network isolation and authentication, then adding encryption and audit logging.
Enterprise-Grade Cache Security
Cachee.ai includes built-in encryption, authentication, audit logging, and compliance tools out of the box.
Start Free Trial