Skip to content

Policy-Driven Crypto-Agility Fundamentals

Target Audience: Solution Architects, Security Architects, Operations Engineers

Reading Time: 12 minutes

Prerequisites: Understanding of cryptographic algorithms, key management basics


Executive Summary

Crypto-agility is the ability to rapidly change cryptographic algorithms without modifying application code. ANKASecure achieves this through KID abstraction, policy-driven algorithm selection, and zero-code migration via re-encryption and re-signing APIs.

Why it matters: Cryptographic algorithms have limited lifespans. Organizations must be able to migrate from compromised or deprecated algorithms (e.g., RSA-1024 → RSA-2048, or RSA-2048 → ML-KEM-768) without rewriting applications.


Table of Contents

  1. The Problem: Crypto-Rigidity
  2. What is Crypto-Agility?
  3. KID Abstraction Layer
  4. Policy-Driven Algorithm Selection
  5. Zero-Code Migration
  6. Key Rotation Strategies
  7. Implementation Examples
  8. Best Practices

The Problem: Crypto-Rigidity

Traditional Approach (Hardcoded Algorithms)

Typical application code:

// BAD: Algorithm hardcoded in application
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] ciphertext = cipher.encrypt(plaintext);

Problems:

  1. Algorithm is hardcoded - Changing from RSA to ML-KEM requires code changes
  2. Key type is fixed - Cannot switch key types without refactoring
  3. No abstraction - Application tightly coupled to cryptographic implementation
  4. Migration is expensive - Must modify, test, and redeploy all applications

Real-World Scenarios

Scenario 1: Algorithm deprecation

  • NIST deprecates SHA-1 (2017)
  • All applications using SHA-1 must be updated
  • Cost: Millions in development, testing, deployment

Scenario 2: Quantum threat

  • Quantum computers threaten RSA/ECC (2030 deadline)
  • All RSA/ECC applications must migrate to PQC
  • Cost: Astronomical if algorithms are hardcoded

Scenario 3: Security vulnerability

  • New attack discovered against AES-CBC (hypothetical)
  • Must migrate to AES-GCM immediately
  • Cost: Emergency code changes, rushed deployments, high risk

The Cost of Crypto-Rigidity

Migration Type Crypto-Rigid Cost Crypto-Agile Cost Savings
SHA-1 → SHA-256 6-12 months, $2M-5M 1 week, $50K 95%+
RSA-2048 → ML-KEM-768 12-24 months, $10M-20M 2-4 weeks, $200K 98%+
AES-CBC → AES-GCM 3-6 months, $1M-2M 1 week, $50K 97%+

ANKASecure goal: Reduce migration cost by 95%+ through crypto-agility.


What is Crypto-Agility?

Definition

Crypto-agility is the ability to change cryptographic algorithms, key sizes, or security parameters without modifying application source code.

Key principles:

  1. Algorithm abstraction - Applications reference keys by identifier, not algorithm
  2. Policy-driven selection - Algorithm chosen based on policies, not code
  3. Transparent migration - Re-encrypt/re-sign without exposing plaintext
  4. Backward compatibility - Old ciphertexts remain decryptable during transition

Crypto-Agile Architecture

Application Code
    ↓
  [KID Reference]  ← Abstract identifier, no algorithm
    ↓
Policy Layer
    ↓
  [Algorithm Selection]  ← Policy determines algorithm
    ↓
Cryptographic Engine
    ↓
  [Actual Encryption/Signing]

Application perspective:

// GOOD: Crypto-agile code
String ciphertext = client.encrypt("my-key-id", plaintext);

No algorithm mentioned in code. Platform determines algorithm based on key metadata and policies.

Benefits

Benefit Description
Future-proof Easily adopt new algorithms (PQC, post-PQC)
Rapid response Quickly migrate from compromised algorithms
Cost reduction Avoid expensive code changes and redeployments
Compliance Meet evolving regulatory requirements (CNSA 2.0, FIPS)
Risk mitigation Hedge against cryptanalysis breakthroughs

KID Abstraction Layer

What is a KID?

KID (Key Identifier): An abstract, user-friendly identifier for a cryptographic key.

Examples:

  • my-encryption-key
  • payment-signing-key
  • user-data-encryption-2025

Key insight: KID does not reveal the underlying algorithm.

KID vs Algorithm

Traditional approach:

Key: rsa-2048-key-001
Algorithm: RSA-2048 (embedded in name)
Problem: Changing to ML-KEM requires renaming key everywhere

Crypto-agile approach (ANKASecure):

KID: my-encryption-key
Algorithm: [metadata, not in identifier]
Benefit: Change algorithm without changing KID

How KID Abstraction Works

Key generation:

POST /api/v3/key-management/keys

Request:
{
  "kid": "my-encryption-key",
  "kty": "RSA",
  "alg": "RSA-3072",
  "keyOps": ["encrypt", "decrypt"]
}

Usage (application code):

// Application never mentions "RSA"
String jwe = client.encrypt("my-encryption-key", plaintext);

Internal mapping (platform):

KID: "my-encryption-key"
  → UUID: "550e8400-e29b-41d4-a716-446655440000"
  → Tenant: "tenant-a"
  → Algorithm: "RSA-3072" (JWE key management: RSA-OAEP-512)
  → Keystore path: "/srv/anka-secure/.../my-encryption-key.p12"

Migration (zero code changes):

# 1. Generate new key with PQC algorithm
POST /api/v3/admin/tenants/{tenantId}/keys
{
  "kid": "my-encryption-key-v2",
  "kty": "ML-KEM",
  "algorithm": "ML-KEM-768"
}

# 2. Re-encrypt data (the source key is read from the JWE header)
POST /api/v3/crypto/reencrypt
{
  "jweToken": { "protected": "...", "recipients": [ ... ], "iv": "...", "ciphertext": "...", "tag": "..." },
  "newKid": "my-encryption-key-v2"
}

# 3. Update references (configuration, not code)
# Old: encryption_key=my-encryption-key
# New: encryption_key=my-encryption-key-v2

# 4. Application code unchanged
String jwe = client.encrypt(config.encryptionKey, plaintext);

See: KID System Explained


Policy-Driven Algorithm Selection

Types of Policies

ANKASecure uses three policy layers to control cryptographic behavior:

Policy Type Purpose Example
Algorithm Availability Which algorithms are allowed "Only FIPS-approved algorithms"
Algorithm Rotation When to migrate to new algorithms "Migrate RSA to ML-KEM by 2030"
Key Lifecycle When keys expire and rotate "Rotate keys every 90 days"

Algorithm Availability Policy

Purpose: Control which algorithms can be used for new keys.

Example policy:

{
  "policyName": "PQC-Transition-2025",
  "allowedAlgorithms": {
    "encryption": ["ML-KEM-768", "ML-KEM-1024", "AES-256-GCM"],
    "signature": ["ML-DSA-65", "ML-DSA-87"],
    "composite": ["P-256+ML-KEM-768", "P-384+ML-KEM-768"]
  },
  "deprecatedAlgorithms": ["RSA-2048", "ECDSA-P-256"],
  "effectiveDate": "2025-01-01"
}

Enforcement:

# Attempt to generate RSA-2048 key (deprecated)
POST /api/v3/admin/tenants/{tenantId}/keys
{
  "kid": "my-key",
  "algorithm": "RSA-2048"
}

Response: HTTP 403 Forbidden
{
  "error": "Algorithm RSA-2048 is deprecated by policy PQC-Transition-2025",
  "allowedAlgorithms": ["ML-KEM-768", "ML-KEM-1024", ...]
}

Algorithm Rotation Policy

Purpose: Define migration timelines from deprecated to approved algorithms.

Example policy:

{
  "policyName": "RSA-to-PQC-Migration",
  "rotations": [
    {
      "fromAlgorithm": "RSA-2048",
      "toAlgorithm": "ML-KEM-768",
      "deadline": "2030-01-01",
      "migrationPath": "reencrypt"
    },
    {
      "fromAlgorithm": "ECDSA-P-256",
      "toAlgorithm": "ML-DSA-65",
      "deadline": "2030-01-01",
      "migrationPath": "resign"
    }
  ]
}

Enforcement:

  • Phase 1 (Warning): API returns warning header: X-Algorithm-Deprecated: RSA-2048 (migrate by 2030-01-01)
  • Phase 2 (Soft block): Require explicit override flag
  • Phase 3 (Hard block): Reject operations after deadline

Key Lifecycle Policy

Purpose: Control key expiration, rotation frequency, and archival.

Example policy:

{
  "policyName": "High-Security-Keys",
  "maxKeyAge": "90 days",
  "maxOperations": 1000000,
  "rotationStrategy": "automatic",
  "archivalPeriod": "7 years"
}

Behavior:

  • Day 0-89: Key active
  • Day 90: Key auto-rotated (new key generated, old key deprecated)
  • Day 90+: Old key decrypts only (cannot encrypt)
  • Year 7: Old key archived (cold storage)

See: Policy System Design*


Zero-Code Migration

The Goal

Migrate from Algorithm A to Algorithm B without:

  • ❌ Modifying application code
  • ❌ Exposing plaintext to client
  • ❌ Storing plaintext on server disk
  • ❌ Requiring application downtime

ANKASecure achieves this through server-side re-encryption and re-signing.

Re-Encryption (Algorithm Migration)

Problem: Data encrypted with RSA-2048, need to migrate to ML-KEM-768.

Traditional approach (INSECURE):

1. Decrypt with RSA-2048 → plaintext (client-side)
2. Encrypt with ML-KEM-768 → new ciphertext (client-side)
Problem: Plaintext exposed to client

ANKASecure approach (SECURE):

1. Send old JWE token to /api/v3/crypto/reencrypt
2. Server decrypts with RSA-2048 (in memory, never leaves server)
3. Server encrypts with ML-KEM-768 (in memory)
4. Return new JWE token
5. Plaintext NEVER sent to client or written to disk

Endpoint:

POST /api/v3/crypto/reencrypt

Request (the source key is read from the JWE header):
{
  "jweToken": { "protected": "...", "recipients": [ ... ], "iv": "...", "ciphertext": "...", "tag": "..." },
  "newKid": "mlkem-key"
}

Response:
{
  "jweToken": { "protected": "...", "recipients": [ ... ], "iv": "...", "ciphertext": "...", "tag": "..." },
  "oldKeyRequested": "rsa-key",
  "oldKeyAlgorithmUsed": "RSA-2048",
  "newKeyRequested": "mlkem-key",
  "newKeyAlgorithmUsed": "ML-KEM-768"
}

Streaming mode (large files):

POST /api/v3/crypto/stream/reencrypt?newKid=mlkem-key

Request (multipart, IN THIS ORDER):

- header: [old JWET header JSON]
- file: [old ciphertext stream]

Response (multipart/mixed):

- PART 1: [new JWET header JSON + new ciphertext stream]
- PART 2: [end-of-stream verdict JSON]

Memory: O(1) constant (2 MiB dual-thread pipeline).

See: Flow 4 - Asymmetric Re-encrypt (Streaming)

Re-Signing (Algorithm Migration)

Problem: Data signed with RSA-PSS-256, need to migrate to ML-DSA-65.

Traditional approach (INSECURE):

1. Verify old signature with RSA-PSS-256
2. Send payload to client
3. Client signs with ML-DSA-65
Problem: Payload exposed to client

ANKASecure approach (SECURE):

1. Send old JWS + payload stream to /api/v3/crypto/resign
2. Server verifies old signature (RSA-PSS-256)
3. Server signs with new key (ML-DSA-65)
4. Return new JWS
5. Payload streamed through, never stored

Endpoint:

POST /api/v3/crypto/stream/resign

Request (multipart):

- metadata: {"oldJws": "[old detached JWS JSON]", "newKid": "mldsa-key"}
- file: [payload stream]

Response (multipart/mixed):

- PART 1: {"protected": "eyJhbGci...ML-DSA-65", "signature": "new-signature"}
- PART 2: [end-of-stream verdict JSON]

See: Flow 12 - Stream Re-sign


Key Rotation Strategies

Time-Based Rotation

Strategy: Rotate keys every N days/months.

Use cases:

  • Compliance requirements (e.g., PCI-DSS: rotate every 90 days)
  • Limit key exposure window
  • Proactive security posture

Implementation:

{
  "rotationPolicy": "time-based",
  "rotationInterval": "90 days",
  "autoRotate": true
}

Behavior:

  • Day 0: Key my-key-v1 active
  • Day 90: Auto-generate my-key-v2, deprecate my-key-v1
  • Day 90+: New encryptions use my-key-v2, old ciphertexts decrypt with my-key-v1
  • Day 180: Auto-generate my-key-v3, deprecate my-key-v2

Usage-Based Rotation

Strategy: Rotate keys after N operations.

Use cases:

  • High-volume systems (millions of operations/day)
  • Limit cryptanalysis opportunities
  • Reduce risk per key

Implementation:

{
  "rotationPolicy": "usage-based",
  "maxOperations": 1000000,
  "autoRotate": true
}

Behavior:

  • Operations counter: 0 → 1,000,000
  • At 1,000,000: Auto-rotate key
  • Old key: decrypt-only mode

Event-Based Rotation

Strategy: Rotate keys after security incidents or detected anomalies.

Use cases:

  • Breach detection
  • Suspicious activity alerts
  • Insider threat mitigation

Implementation:

# Manual rotation trigger (the Stable KID in the path is kept; new material becomes primary)
POST /api/v3/admin/tenants/{tenantId}/keys/my-key/rotations
{
  "newMaterial": {
    "kty": "ML-KEM",
    "alg": "ML-KEM-1024"
  }
}

Response:
{
  "kid": "my-key",
  "kty": "ML-KEM",
  "alg": "ML-KEM-1024",
  "status": "active",
  "createdAt": "2025-01-15T10:30:00Z",
  "primaryMaterialVersion": 2
}

Algorithm Migration Rotation

Strategy: Rotate to upgrade algorithm (e.g., RSA → ML-KEM).

Use cases:

  • CNSA 2.0 compliance (PQC by 2030)
  • Algorithm deprecation
  • Security level upgrade

Implementation:

# Generate new key with PQC algorithm
POST /api/v3/admin/tenants/{tenantId}/keys
{
  "kid": "my-key-v2",
  "kty": "ML-KEM",
  "algorithm": "ML-KEM-768"
}

# Re-encrypt each stored JWE (one request per token; there is no batch route)
POST /api/v3/crypto/reencrypt
{
  "jweToken": { "protected": "...", "recipients": [ ... ], "iv": "...", "ciphertext": "...", "tag": "..." },
  "newKid": "my-key-v2"
}

See: Key Rotation Strategies*


Implementation Examples

Example 1: Simple Crypto-Agile Application

Configuration file (config.yml):

encryption:
  keyId: my-encryption-key  # Abstract KID, no algorithm

signature:
  keyId: my-signing-key  # Abstract KID, no algorithm

Application code:

// Load configuration
Config config = ConfigLoader.load("config.yml");

// Encrypt (algorithm determined by platform)
String ciphertext = client.encrypt(config.encryption.keyId, plaintext);

// Sign (algorithm determined by platform)
String signature = client.sign(config.signature.keyId, payload);

Migration (change config, not code):

# Before (RSA-2048)
encryption:
  keyId: my-encryption-key-rsa

# After (ML-KEM-768) - only config changed
encryption:
  keyId: my-encryption-key-mlkem

Application code: Unchanged.

Example 2: Automated Re-Encryption Pipeline

Scenario: Migrate 1 million encrypted records from RSA to ML-KEM.

Script:

import ankatech_sdk as anka

client = anka.Client(config)

# Fetch all encrypted records
records = database.query("SELECT id, jwe FROM encrypted_data")

# Re-encrypt in batches
for batch in chunks(records, batch_size=1000):
    results = client.batch_reencrypt(
        old_kid="rsa-key",
        new_kid="mlkem-key",
        jwes=[record.jwe for record in batch]
    )

    # Update database
    for record, result in zip(batch, results):
        database.update(
            "UPDATE encrypted_data SET jwe = ? WHERE id = ?",
            result.jwe, record.id
        )

print("Migration complete: 1,000,000 records re-encrypted")

Time: ~2 hours (streaming mode, parallel processing).

Plaintext exposure: Zero (all operations server-side).

Example 3: Policy-Driven Key Generation

Policy-aware key generation:

// The application never names an algorithm. It provisions a key by PURPOSE --
// through the Admin Console, ankasecure-km or the Admin API -- and the
// deployment's algorithm policy decides what backs it. The data-plane SDK then
// names that key by kid and never sees the choice.
KeyMetadata key = client.getKeyMetadata("user-data-encryption");

System.out.println("Key: " + key.getKid());
System.out.println("Algorithm: " + key.getAlg());   // ML-KEM-768, selected by policy

Policy layer selects algorithm:

  • Security Level 3 → NIST Level 3
  • Encryption usage → KEM algorithm
  • Current policy → ML-KEM-768 (PQC-approved)

Result: Application gets crypto-agile key without specifying algorithm.


Best Practices

1. Use Abstract KIDs

DO:

my-encryption-key
payment-signing-key
user-data-key-2025

DON'T:

rsa-2048-key
aes-256-gcm-key
mlkem-768-encryption

Reason: Algorithm should not be in identifier (prevents crypto-agility).

2. Store Algorithm in Metadata, Not Names

DO:

{
  "kid": "my-key",
  "algorithm": "ML-KEM-768",
  "metadata": {
    "purpose": "user-data-encryption",
    "securityLevel": 192
  }
}

DON'T:

{
  "kid": "my-mlkem768-key",
  "purpose": "encryption"
}

3. Use Policies to Control Algorithms

DO: Define organizational policy, enforce via platform.

DON'T: Hardcode algorithm choices in application code.

4. Plan for Migration Windows

Recommendation: 6-12 month migration windows for algorithm changes.

Phases:

  1. Month 0-3: Generate new keys, dual-key operation
  2. Month 3-9: Re-encrypt/re-sign existing data
  3. Month 9-12: Deprecate old keys, verify complete migration
  4. Month 12+: Archive old keys (compliance retention)

5. Monitor Key Usage

Metrics to track:

  • Operations per key (detect anomalies)
  • Key age (trigger rotation)
  • Algorithm distribution (track migration progress)
  • Failed decryptions (detect issues)

6. Test Migration Before Enforcement

Approach:

  1. Generate test keys with new algorithm
  2. Test encryption/decryption in staging
  3. Benchmark performance
  4. Validate interoperability
  5. Only then enforce in production

7. Maintain Backward Compatibility

Rule: Always support decryption with old keys during migration.

Implementation:

  • Keep deprecated keys in decrypt-only mode
  • Don't delete keys until compliance retention period expires
  • Support multiple key versions simultaneously

Quick Reference

Crypto-Agility Checklist

Design phase:

  • [ ] Use abstract KIDs (no algorithms in names)
  • [ ] Externalize key configuration (config files, not code)
  • [ ] Design for multiple key versions

Implementation phase:

  • [ ] Use platform APIs (not low-level crypto libraries)
  • [ ] Never hardcode algorithms in code
  • [ ] Implement configuration-driven key selection

Operations phase:

  • [ ] Define algorithm availability policies
  • [ ] Plan rotation schedules
  • [ ] Monitor key usage metrics
  • [ ] Test migration procedures

Migration phase:

  • [ ] Use re-encryption/re-signing APIs
  • [ ] Batch operations for large datasets
  • [ ] Verify plaintext never leaves server
  • [ ] Maintain audit trail

Foundational Concepts

Key Management

  • Key Rotation Strategies*
  • Key Lifecycle Policy*
  • Algorithm Availability Policy*
  • Algorithm Rotation Policy*
  • Policy System Design*

SDK Examples

Use Cases

  • Crypto-Agility (Technical)*
  • Crypto-Agility (Executive)*

Document Status: ✅ ACTIVE

Last Updated: January 2025

Document Location: 02-concepts/crypto-agility-fundamentals.md

Target Audience: Solution Architects, Security Architects, Operations Engineers

Feedback: For corrections or additions, contact the documentation team.

* restricted content — see higher-tier documentation