← Home ← Codex ← DEBT ← Engine
Browse by Category
+ added · updated 7d
← Back to glossary

Cryptographic Agility

Cryptography Advanced
debt(d5/e7/b7/t7)
d5 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'specialist tool catches' (d5). Semgrep and phpstan patterns can flag hardcoded weak algorithms like md5/sha1/des, but the deeper absence of agility (no version prefix on ciphertext, no policy centralisation) is invisible to linters and only surfaces during a forced migration.

e7 Effort Remediation debt — work required to fix once spotted

Closest to 'cross-cutting refactor across the codebase' (e7). The quick_fix requires prefixing every ciphertext/hash/token with a version identifier, centralising algorithm policy, AND adding a background re-encryption job — this touches every call site and every stored record, not one component.

b7 Burden Structural debt — long-term weight of choosing wrong

Closest to 'strong gravitational pull' (b7). Applies across web, cli, queue-worker, and library contexts; every crypto operation must go through the policy layer, and stored data formats carry the versioning commitment forward indefinitely.

t7 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'serious trap' (t7). The misconception is explicit: developers think agility means 'let callers pick an algorithm', which is the exact pattern that produces JWT alg:none and algorithm confusion attacks — the intuitive reading is the dangerous one.

About DEBT scoring →

Also Known As

crypto agility algorithm agility cipher agility

TL;DR

The ability to swap cryptographic algorithms, key sizes, and protocols without rewriting application code or breaking deployed data.

Explanation

Cryptographic agility is a design property: your system can migrate from one algorithm to another (AES-GCM to ChaCha20-Poly1305, SHA-256 to SHA-3, RSA to post-quantum KEMs like ML-KEM) without a rewrite. History makes this non-negotiable — MD5, SHA-1, DES, 3DES, RC4, and PKCS#1 v1.5 padding were all standard once and are now broken or deprecated. NIST's post-quantum standardization (FIPS 203/204/205 in 2024) means every asymmetric cryptosystem currently in production will eventually need replacement.

Agility has three concrete requirements. First, algorithm identifiers travel with ciphertext, hashes, and tokens. Password hashes prefix the algorithm (`$argon2id$...`, `$2y$...`), JWTs declare `alg` in the header, TLS negotiates cipher suites, and encrypted payloads carry a version byte or algorithm OID. Second, the code path branches on that identifier rather than hardcoding a call. Third, key material is versioned so you can decrypt old data with the old algorithm while encrypting new data with the new one — envelope encryption with per-record key IDs makes this manageable.

Operationally, agility means dual-write during migration: read supports old and new, write emits only new, then a background job re-encrypts or re-hashes lazy-loaded records. `password_needs_rehash()` in PHP is the canonical example — on successful login you check whether the stored hash uses current parameters and upgrade transparently.

What agility is not: it is not writing your own crypto abstraction over libsodium, nor exposing algorithm choice to callers, nor supporting every algorithm forever. Deprecated algorithms should be readable but never writable, and eventually removed after full migration. The goal is that when SHA-3, Argon2, or ML-KEM is mandated by policy, you ship a config change and a migration job — not a rewrite.

Common Misconception

Cryptographic agility means supporting many algorithms at once so users can pick. It actually means the system can migrate off a broken algorithm without a rewrite — the choice is made by policy, not by callers, and old algorithms are read-only during deprecation.

Why It Matters

Every widely deployed algorithm eventually breaks or is deprecated (MD5, SHA-1, DES, RSA-1024, and soon RSA/ECDSA for post-quantum reasons). Systems without agility require emergency rewrites under time pressure — the exact conditions that produce insecure migrations and data loss.

Common Mistakes

  • Hardcoding the algorithm in function calls (`hash('sha256', ...)`, `openssl_encrypt($data, 'aes-256-cbc', ...)`) with no way to change it without editing every call site.
  • Storing ciphertext or hashes without an algorithm identifier or version prefix, making it impossible to know how to decrypt old records after migrating.
  • Treating agility as 'accept any algorithm the client requests' — this is how JWT `alg: none` and algorithm confusion attacks happen.
  • Forgetting to plan the write cutover: reading both old and new but never actually re-encrypting old data, leaving the deprecated algorithm live forever.
  • Building a custom crypto wrapper for agility instead of using vetted libraries (libsodium, JOSE, PASETO) that already handle versioning.

Code Examples

✗ Vulnerable
<?php
// Hardcoded algorithm — no path to migrate off SHA-256 or AES-CBC.

function storePassword(string $password): string {
    // Direct sha256 — broken for passwords AND unversioned.
    return hash('sha256', $password);
}

function encryptData(string $plaintext, string $key): string {
    $iv = random_bytes(16);
    $ct = openssl_encrypt($plaintext, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
    // No algorithm tag, no version — future migration requires
    // decrypting every row to figure out what algorithm it used.
    return base64_encode($iv . $ct);
}

function verifyPassword(string $password, string $stored): bool {
    return hash_equals($stored, hash('sha256', $password));
    // No upgrade path when we move to Argon2.
}
✓ Fixed
<?php
// Agile design: algorithm identifier travels with the data,
// current policy is centralised, old formats stay readable.

final class PasswordHasher {
    // Policy lives in one place; change here to migrate everyone.
    private const CURRENT_ALGO = PASSWORD_ARGON2ID;
    private const CURRENT_OPTS = ['memory_cost' => 65536, 'time_cost' => 4];

    public function hash(string $password): string {
        // password_hash() prefixes the algorithm: $argon2id$..., $2y$...
        return password_hash($password, self::CURRENT_ALGO, self::CURRENT_OPTS);
    }

    public function verify(string $password, string $stored): bool {
        // Reads any supported legacy format (bcrypt, argon2i, argon2id).
        return password_verify($password, $stored);
    }

    public function needsRehash(string $stored): bool {
        // Transparent upgrade on next successful login.
        return password_needs_rehash($stored, self::CURRENT_ALGO, self::CURRENT_OPTS);
    }
}

final class VersionedCipher {
    // Version byte selects the algorithm. Add v2 without touching v1 readers.
    public function encrypt(string $plaintext, string $key): string {
        $nonce = random_bytes(SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES);
        $ct = sodium_crypto_aead_xchacha20poly1305_ietf_encrypt(
            $plaintext, '', $nonce, $key
        );
        return "\x02" . $nonce . $ct; // 0x02 = XChaCha20-Poly1305
    }

    public function decrypt(string $blob, string $key): string {
        $version = ord($blob[0]);
        return match ($version) {
            0x01 => $this->decryptV1AesGcm(substr($blob, 1), $key),   // legacy
            0x02 => $this->decryptV2XChaCha(substr($blob, 1), $key),  // current
            default => throw new RuntimeException("Unknown cipher version: $version"),
        };
    }
    // ... version-specific decrypt methods
}

Added 21 Aug 2026
Views 32
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 2 pings F 4 pings S 1 ping S 0 pings M 0 pings T 2 pings W 0 pings T 0 pings F 0 pings S 0 pings S 1 ping M 0 pings T 0 pings W 1 ping T 0 pings F 1 ping S 1 ping S 0 pings M
No pings yet today
Applebot 1
Google 5 Perplexity 3 PetalBot 2 Applebot 2 Ahrefs 1
crawler 13
DEV INTEL Tools & Severity
🟠 High ⚙ Fix effort: High
⚡ Quick Fix
Prefix every ciphertext, hash, and token with an algorithm/version identifier, centralise the current-algorithm policy in one class, and add a background job that re-encrypts or rehashes data on read.
📦 Applies To
web cli queue-worker library
🔗 Prerequisites
🔍 Detection Hints
hash\('(md5|sha1)'|openssl_encrypt\([^,]+,\s*'(des|rc4|aes-\d+-ecb)'|password_hash\([^,]+,\s*PASSWORD_BCRYPT\)(?!.*needs_rehash)
Auto-detectable: ✓ Yes semgrep phpstan
⚠ Related Problems
🤖 AI Agent
Confidence: Medium False Positives: Medium ✗ Manual fix Fix: High Context: File Tests: Regenerate
CWE-327 CWE-326


✓ schema.org compliant