# All sensitive financial data encrypted at rest using AES-256+

- **Pattern:** `ab-001417` (`finserv-encryption.data-at-rest.aes256-encryption`)
- **Severity:** critical
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001417
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001417)

## Why it matters

Unencrypted financial data at rest — account numbers, SSNs, card tokens, transaction history — is a direct path from database breach to identity theft and fraud. CWE-311 (missing encryption) and CWE-326 (inadequate key length) are both in play when a project stores plaintext or uses AES-128: attackers who exfiltrate the database file get immediately usable cardholder data. NIST SP 800-111 and PCI-DSS 4.0 Req-3.5 require AES-256 or stronger because shorter keys are vulnerable to brute-force as compute costs fall. A single exposed plaintext column is enough to trigger breach notification obligations and PCI fines.

## Severity rationale

Critical because a single unencrypted sensitive column converts a database read vulnerability into direct cardholder data exposure with no further attacker effort required.

## Remediation

Switch to AES-256-GCM for all sensitive fields in `src/lib/encryption.ts` or `src/utils/crypto.ts`. Prefer authenticated encryption (GCM mode) over CBC to also detect tampering. Use the Node.js built-in `crypto` module — do not add a third-party wrapper for this:

```typescript
import crypto from 'node:crypto';

const ALGO = 'aes-256-gcm';

export function encryptField(plaintext: string, key: Buffer): string {
  const iv = crypto.randomBytes(12);
  const cipher = crypto.createCipheriv(ALGO, key, iv);
  const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
  const tag = cipher.getAuthTag();
  return [iv.toString('hex'), tag.toString('hex'), encrypted.toString('hex')].join(':');
}
```

For database-level coverage, enable TDE on your provider (AWS RDS, Supabase) — that covers all columns, including ones you haven't explicitly wrapped. Both approaches together satisfy NIST SC-28 and PCI-DSS 4.0 Req-3.5.

## Detection

- **ID:** `aes256-encryption`
- **Severity:** `critical`
- **What to look for:** Enumerate all sensitive financial data columns (account numbers, transaction history, payment credentials, PII) and count every column that is encrypted vs. unencrypted. Quote the actual encryption algorithm found (e.g., `aes-256-gcm`, `aes-256-cbc`). Check for transparent database encryption (TDE) or application-level encryption. A key size under 256 bits does not count as pass — do not pass if any sensitive field uses weaker than AES-256.
- **Pass criteria:** At least 90% of sensitive financial data columns are encrypted with AES-256 or stronger, either at database level (TDE) or application level. Count all sensitive columns — report the ratio even on pass (e.g., "8 of 8 sensitive columns encrypted with AES-256-GCM"). Quote the actual algorithm and key length used.
- **Fail criteria:** Any sensitive financial data stored unencrypted, or encryption is weaker than AES-256 (e.g., AES-128, DES, basic obfuscation).
- **Skip (N/A) when:** Project is API-only with no data storage, or exclusively delegates storage to third-party payment processor (e.g., Stripe-only — cite the actual processor found, no local customer data).
- **Detail on fail:** Specify which fields lack encryption. Example: `"3 of 5 sensitive columns unencrypted — account_number, ssn, and card_token stored plaintext in customer_data table"`.
- **Cross-reference:** Check `finserv-encryption.data-at-rest.tde-or-column-encryption` for database-level encryption details, and `finserv-encryption.key-management.nist-algorithm-justification` for algorithm selection.
- **Remediation:** Adopt AES-256 encryption for sensitive fields (in `src/lib/encryption.ts` or `src/utils/crypto.ts`). For Node.js, use the `crypto` module. Choose one of two approaches:
  1. **Database-Level (Recommended):** Enable Transparent Data Encryption (TDE) in PostgreSQL, MySQL, or managed cloud databases (AWS RDS, Supabase, etc.).
  2. **Application-Level:** Encrypt sensitive fields before storing:
     ```typescript
     import crypto from 'crypto';
     const cipher = crypto.createCipher('aes-256-cbc', encryptionKey);
     let encrypted = cipher.update(accountNumber, 'utf8', 'hex');
     encrypted += cipher.final('hex');
     await db.insert({ account_number: encrypted });
     ```

## External references

- cwe CWE-311
- cwe CWE-326
- owasp A02
- nist SC-28
- pci-dss Req-3.5
- nist SP-800-111

Taxons: cryptography-and-secrets

HTML version: https://auditbuffet.com/patterns/ab-001417
