1. Regulatory Context: GDPR Article 32 & Article 25
The General Data Protection Regulation (GDPR) establishes data security as a mandatory legal obligation rather than an optional operational preference. Within the SovereignShield Compliance Matrix, cryptographic controls form the bedrock of technical safeguard compliance. Two primary articles govern cryptographic implementations for web applications and cloud architectures:
Article 32: Security of Processing
Article 32(1)(a) mandates that data controllers and processors implement appropriate technical and organizational measures to ensure a level of security appropriate to the risk, explicitly highlighting:
“The pseudonymisation and encryption of personal data…”
Furthermore, Article 32(1)(d) requires:
“A process for regularly testing, assessing, and evaluating the effectiveness of technical and organizational measures for ensuring the security of the processing.”
Article 25: Data Protection by Design and by Default
Article 25 requires engineers to integrate privacy and cryptographic safeguards directly into application logic and database schemas during the initial architectural design phase, rather than attempting to apply perimeter security after deployment. When combined with Article 17 erasure requirements (explored in our GDPR Right to Be Forgotten Database Patterns Guide), field encryption ensures that even archived records remain unreadable.
2. Encryption at Rest: Field-Level Encryption (FLE) vs. Infrastructure Encryption
Why Infrastructure Encryption Is Insufficient
Many development teams rely exclusively on storage-layer encryption (such as AWS EBS Volume Encryption, Azure Storage Encryption, or Cloud SQL Disk Encryption). While disk-level encryption (LUKS, BitLocker) protects physical server hardware against theft from a data center, it provides zero protection against application-layer vulnerabilities:
- An attacker executing a SQL Injection (SQLi) attack receives decrypted records because the database engine transparently decrypts storage upon disk read.
- A compromised database connection string or exposed administrative console (
pgAdmin,DBeaver) exposes all raw PII in plaintext. - Unencrypted backups exported from the database contain exposed user metadata.
Field-Level Encryption Architecture
Field-Level Encryption (FLE) encrypts specific sensitive columns (e.g. email addresses, national tax IDs, financial metrics, IP addresses) in application memory before the payload reaches the database driver. The database stores only ciphertext, initialization vectors (IVs), authentication tags, and key version metadata.
+-------------------+ +-----------------------+ +-----------------------+
| Application Code | ---> | AES-256-GCM Encrypt | ---> | Database Storage |
| (Raw Plaintext PII) | | (Application Memory) | | (Ciphertext + IV + Tag)|
+-------------------+ +-----------------------+ +-----------------------+
3. Node.js & TypeScript AES-256-GCM Implementation
AES-256-GCM (Galois/Counter Mode) is an Authenticated Encryption with Associated Data (AEAD) algorithm. Unlike legacy modes such as CBC (Cipher Block Chaining), GCM generates an authentication tag that detects any unauthorized modification or tampering of the encrypted payload prior to decryption.
Cryptographic Parameters
- Algorithm:
aes-256-gcm - Key Length: 256 bits (32 bytes)
- Initialization Vector (IV): Cryptographically secure random 96-bit (12-byte) buffer generated per record. Never reuse an IV with the same encryption key.
- Auth Tag: 128-bit (16-byte) authentication tag.
Production Key Rotation Engine
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';
export interface EncryptedPayload {
ciphertext: string; // Hex-encoded encrypted data
iv: string; // Hex-encoded 96-bit IV
tag: string; // Hex-encoded 128-bit authentication tag
keyVersion: number; // Version identifier for zero-downtime key rotation
}
export class FieldEncryptionService {
private masterKeys: Map<number, Buffer>;
private activeVersion: number;
/**
* Initializes the encryption service with versioned master keys.
* @param keys Map of version numbers to 64-character hex strings (32 bytes / 256 bits)
* @param activeVersion The key version to use for new encryptions
*/
constructor(keys: Record<number, string>, activeVersion: number) {
this.masterKeys = new Map();
for (const [versionStr, hexKey] of Object.entries(keys)) {
const version = Number(versionStr);
const keyBuffer = Buffer.from(hexKey, 'hex');
if (keyBuffer.length !== 32) {
throw new Error(`[EncryptionService] Master key version ${version} must be exactly 256 bits (32 bytes).`);
}
this.masterKeys.set(version, keyBuffer);
}
if (!this.masterKeys.has(activeVersion)) {
throw new Error(`[EncryptionService] Active key version ${activeVersion} is not configured.`);
}
this.activeVersion = activeVersion;
}
/**
* Encrypts plaintext string using AES-256-GCM
*/
public encrypt(plaintext: string): EncryptedPayload {
if (typeof plaintext !== 'string' || plaintext.length === 0) {
throw new Error('[EncryptionService] Cannot encrypt empty payload.');
}
// Generate 96-bit IV (12 bytes) using CSPRNG
const iv = randomBytes(12);
const key = this.masterKeys.get(this.activeVersion)!;
const cipher = createCipheriv('aes-256-gcm', key, iv);
let ciphertext = cipher.update(plaintext, 'utf8', 'hex');
ciphertext += cipher.final('hex');
const tag = cipher.getAuthTag().toString('hex');
return {
ciphertext,
iv: iv.toString('hex'),
tag,
keyVersion: this.activeVersion,
};
}
/**
* Decrypts ciphertext and verifies authenticity tag.
* Supports legacy key versions to enable zero-downtime key rotation.
*/
public decrypt(payload: EncryptedPayload): string {
const key = this.masterKeys.get(payload.keyVersion);
if (!key) {
throw new Error(`[EncryptionService] Key version ${payload.keyVersion} not found for decryption.`);
}
const decipher = createDecipheriv(
'aes-256-gcm',
key,
Buffer.from(payload.iv, 'hex')
);
// Set the expected authentication tag
decipher.setAuthTag(Buffer.from(payload.tag, 'hex'));
try {
let plaintext = decipher.update(payload.ciphertext, 'hex', 'utf8');
plaintext += decipher.final('utf8');
return plaintext;
} catch (err) {
throw new Error('[EncryptionService] Decryption failed. Payload may be tampered or corrupted.');
}
}
/**
* Re-encrypts payload to update it to the active master key version
*/
public reencrypt(payload: EncryptedPayload): EncryptedPayload {
if (payload.keyVersion === this.activeVersion) {
return payload; // Already using latest key
}
const decryptedText = this.decrypt(payload);
return this.encrypt(decryptedText);
}
}
4. PostgreSQL Database Integration (pgcrypto)
For systems performing field encryption inside database triggers or stored procedures, PostgreSQL provides the pgcrypto extension.
View the complete pgcrypto Pseudonymization Reference Implementation on our GitHub
-- Enable pgcrypto extension
CREATE EXTENSION IF NOT EXISTS pgcrypto;
-- Table definition storing PII with AES-256 symmetric encryption
CREATE TABLE sensitive_user_profiles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
organization_id UUID NOT NULL,
-- Store encrypted PII as bytea
encrypted_email BYTEA NOT NULL,
encrypted_ssn BYTEA NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Insert record encrypted with AES-256-CBC / GCM key
INSERT INTO sensitive_user_profiles (organization_id, encrypted_email, encrypted_ssn)
VALUES (
'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11',
pgp_sym_encrypt('user@example.com', 'SUPER_SECRET_DB_MASTER_KEY', 'cipher-algo=aes256'),
pgp_sym_encrypt('999-00-1234', 'SUPER_SECRET_DB_MASTER_KEY', 'cipher-algo=aes256')
);
-- Query and decrypt record
SELECT
id,
organization_id,
pgp_sym_decrypt(encrypted_email, 'SUPER_SECRET_DB_MASTER_KEY') AS email,
pgp_sym_decrypt(encrypted_ssn, 'SUPER_SECRET_DB_MASTER_KEY') AS ssn
FROM sensitive_user_profiles
WHERE id = 'SOME_PROFILE_UUID';
5. Encryption in Transit: Hardened TLS 1.3 Web Server Setup
All data transmitted across public or internal networks must be secured using Transport Layer Security (TLS). GDPR enforcement authorities consider TLS 1.0 and TLS 1.1 obsolete and non-compliant due to vulnerabilities such as POODLE, BEAST, and CRIME.
NGINX TLS 1.3 Production Configuration
# /etc/nginx/sites-available/sovereignshield.conf
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name api.sovereignshieldapp.com;
# SSL Certificate Paths (Let's Encrypt / Custom CA)
ssl_certificate /etc/letsencrypt/live/api.sovereignshieldapp.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.sovereignshieldapp.com/privkey.pem;
# Strictly enforce TLS 1.2 and TLS 1.3 exclusively
ssl_protocols TLSv1.2 TLSv1.3;
# High-security AEAD Cipher Suites with Perfect Forward Secrecy (PFS)
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# HSTS - HTTP Strict Transport Security (2 Years + Subdomains + Preload)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# Defensive HTTP Security Headers
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Session Ticket & Caching Optimization
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
location / {
proxy_pass http://127.0.0.1:4321;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
}
6. Verification Checklist & Compliance Matrix
| Requirement Target | Technical Standard | GDPR Article | Verification Method |
|---|---|---|---|
| Field Encryption | AES-256-GCM AEAD | Article 32(1)(a) | Unit test suite verifies payload encryption |
| Tamper Prevention | 128-bit Auth Tag verification | Article 32(1)(b) | Test invalid auth tag throws error |
| Key Versioning | Envelope key tags (e.g. keyVersion: 2) |
Article 32(1)(d) | Zero-downtime key rotation pipeline test |
| Network Security | TLS 1.3 / TLS 1.2 with PFS | Article 32(1)(a) | npx testssl.sh api.sovereignshieldapp.com |
| HSTS Enforcement | max-age=63072000; includeSubDomains |
Article 25 | curl -I https://sovereignshieldapp.com |
7. Automated Cryptographic Unit Tests (Jest/Vitest)
// tests/unit/encryption.test.ts
import { describe, it, expect } from 'vitest';
import { FieldEncryptionService } from '../../src/utils/FieldEncryptionService';
const TEST_KEYS = {
1: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef',
2: 'fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210'
};
describe('FieldEncryptionService (AES-256-GCM)', () => {
const service = new FieldEncryptionService(TEST_KEYS, 1);
it('should encrypt and decrypt plaintext successfully', () => {
const rawPII = 'patient-ssn-999-00-1234';
const encrypted = service.encrypt(rawPII);
expect(encrypted.ciphertext).not.toBe(rawPII);
expect(encrypted.keyVersion).toBe(1);
const decrypted = service.decrypt(encrypted);
expect(decrypted).toBe(rawPII);
});
it('should throw error when auth tag is tampered with', () => {
const encrypted = service.encrypt('sensitive-data');
encrypted.tag = '00000000000000000000000000000000'; // Corrupt tag
expect(() => service.decrypt(encrypted)).toThrow(/Decryption failed/);
});
it('should seamlessly decrypt older key versions during key rotation', () => {
const v1Service = new FieldEncryptionService(TEST_KEYS, 1);
const v1Payload = v1Service.encrypt('legacy-secret');
const v2Service = new FieldEncryptionService(TEST_KEYS, 2);
const decrypted = v2Service.decrypt(v1Payload);
expect(decrypted).toBe('legacy-secret');
// Re-encrypt to update key version
const updatedPayload = v2Service.reencrypt(v1Payload);
expect(updatedPayload.keyVersion).toBe(2);
});
});
Conclusion & Architecture Recommendations
Implementing robust data protection for GDPR compliance requires combining field-level authenticated encryption (AES-256-GCM) at rest with hardened transport-layer security (TLS 1.3) in transit. Relying solely on disk volume encryption leaves applications vulnerable to application-level attacks. Developers should adopt application-layer envelope key rotation, enforce HSTS preload headers, and maintain automated cryptographic test suites across all production services.
To expand your technical compliance infrastructure:
- Review deletion and tombstone lifecycle management in our GDPR Right to Be Forgotten Database Patterns Guide.
- Implement complementary US healthcare access rules with our HIPAA Access Control & RBAC Guide.
- Build tamper-evident log streams with the HIPAA Audit Logging for SaaS Guide.
- Validate your cryptographic hashes in real-time with the Auditor Presentation Portal & Verifier.
- Deploy automated security infrastructure using our Remediation Roadmap & Terraform Generator.