Legacy data migration plan for re-encryption
Why it matters
CWE-311 applies to legacy data as much as to new records: financial systems that added encryption requirements mid-lifecycle often have years of plaintext records in production tables that were never backfilled. PCI-DSS 4.0 Req-3.4 requires that stored PANs be unreadable anywhere they are stored — legacy records are not exempt. NIST SC-28 requires confidentiality of information at rest regardless of when it was written. A re-encryption plan with 0% progress means the encryption controls that apply to new records do not protect the existing dataset, which is typically the most valuable target for attackers exfiltrating historical financial data. Without a tracked migration plan, legacy exposure compounds with every passing month.
Severity rationale
Info because legacy data without a re-encryption plan is an inherited technical debt gap rather than an active development defect, but it represents a compliance scope gap under PCI-DSS Req-3.4 that cannot be waived.
Remediation
Audit existing tables for unencrypted sensitive columns, then build an incremental re-encryption migration with rollback capability:
// scripts/re-encrypt-legacy.ts
import { db } from '../src/lib/db';
import { encryptField } from '../src/lib/encryption';
const BATCH_SIZE = 1000;
async function reEncryptAccounts() {
let offset = 0;
let processed = 0;
while (true) {
const rows = await db.query(
`SELECT id, account_number FROM accounts
WHERE encrypted_at IS NULL LIMIT $1 OFFSET $2`,
[BATCH_SIZE, offset]
);
if (rows.length === 0) break;
for (const row of rows) {
const encrypted = await encryptField(row.account_number);
await db.query(
'UPDATE accounts SET account_number = $1, encrypted_at = NOW() WHERE id = $2',
[encrypted, row.id]
);
}
processed += rows.length;
console.log(`Re-encrypted ${processed} records`);
offset += BATCH_SIZE;
}
}
Document progress in docs/legacy-migration.md with phase dates and record counts. Keep unencrypted backups in isolated storage with restricted access until migration is complete and verified — do not delete them until re-encryption integrity is confirmed.
Detection
- ID:
legacy-data-migration-plan - Severity:
info - What to look for: Count all legacy data tables or records that predate current encryption standards. Look for migration plans with progress tracking. Quote the actual migration status and percentage found. Enumerate phases and completion dates.
- Pass criteria: If legacy data exists, at least 1 re-encryption plan is documented with progress at 50% or higher. If no legacy data exists, at least 1 document explicitly states "greenfield — no legacy data." Report the count even on pass (e.g., "Migration plan found: 3 phases, 85% complete, 1.8M of 2.1M records re-encrypted").
- Fail criteria: Legacy data exists without a re-encryption plan (0 plans documented), or plan exists but progress is 0%.
- Skip (N/A) when: Greenfield project with no legacy data to migrate — cite the actual project creation date or first commit date found.
- Detail on fail:
"Legacy data exists in 2 tables but 0 re-encryption plans documented"or"Migration plan from 2025-01 shows 0% progress — 2.3M records still unencrypted" - Remediation:
- Create a legacy data migration plan:
# Legacy Data Re-Encryption Plan ## Scope - 2.3M customer records in PostgreSQL - Current encryption: None (plaintext) - Target encryption: AES-256-GCM ## Timeline - Phase 1 (Jan-Feb 2025): Audit and classify data, build re-encryption pipeline - Phase 2 (Mar-Apr 2025): Re-encrypt 500K test records, verify integrity - Phase 3 (May-Jun 2025): Re-encrypt remaining 1.8M records - Phase 4 (Jul 2025): Verify all records, purge unencrypted backups ## Progress - Phase 1: 30% complete (Audit done, pipeline in progress) - Next milestone: Complete re-encryption pipeline (due Feb 15) ## Rollback Plan - Keep unencrypted backup for 30 days post-completion - Encryption is idempotent — can re-encrypt without downtime
- Create a legacy data migration plan:
External references
- cwe · CWE-311 — Missing Encryption of Sensitive Data
- pci-dss:4.0 · Req-3.4 — Render PAN unreadable anywhere stored
- nist:rev5 · SC-28 — Protection of Information at Rest
Taxons
History
- 2026-04-18·v1.0.0·Initial import from finserv-encryption·automated