Skip to content

Flow 34 – Composite Key Rotation (COMPOSITE_KEM_COMBINE)

Rotate quantum-resistant composite keys while maintaining hybrid mode and component algorithms for continued HNDR protection.

Real-World Scenarios:

  • Scheduled rotation of production hybrid keys maintaining quantum resistance
  • Compliance-driven key refresh without algorithm changes
  • Zero-downtime rotation for critical encryption services
  • Rotation chain audit trails for regulatory compliance

Key Algorithms:

  • X25519: Classical ECDH key agreement providing efficient key exchange (NIST Level 3)
  • ML-KEM-768: Post-quantum KEM providing 192-bit security against quantum attacks (NIST FIPS 203)

API Endpoints:

  • POST /api/key-management/keys - Generate composite key (unified endpoint)
  • POST /api/key-management/keys/{kid}/rotations - Rotate composite key material (kid unchanged)
  • POST /api/crypto/encrypt - Encrypt with the rotated key (transparent material resolution)

Steps:

  1. Generate original composite key (X25519 + ML-KEM-768, COMPOSITE_KEM_COMBINE)
  2. Rotate the key material to a new version using rotateKey() — the Stable KID stays the same
  3. Verify the kid is unchanged and the material version advanced (previous version RETIRED, new version PRIMARY)
  4. Verify the new material is PRIMARY under the same Stable KID
  5. Demonstrate encryption transparently uses the new PRIMARY material

When to use:

  • Scheduled key rotation maintaining quantum-resistant security
  • Compliance requirements for periodic key refresh
  • Zero-downtime rotation without re-encrypting historical data
  • Audit trails proving proper key lifecycle management

Dependency — this example imports co.ankatech.ankasecure.sdk.examples.ExampleUtil. If you have not copied that class yet, see example-util.md.


Complete Java Implementation

Source: src/main/java/co/ankatech/ankasecure/sdk/examples/ExampleScenario34.java

package co.ankatech.ankasecure.sdk.examples;

import co.ankatech.ankasecure.sdk.AuthenticatedSdk;
import co.ankatech.ankasecure.sdk.model.*;
import co.ankatech.secure.client.model.KeyRequest;

import java.nio.file.Path;

import static co.ankatech.ankasecure.sdk.examples.ExampleUtil.*;

/**
 * Scenario 34 — Composite Key Rotation and Migration.
 *
 * <p>Demonstrates key rotation patterns for composite keys including:</p>
 * <ul>
 *   <li>Rotating from simple to composite keys (classical → hybrid)</li>
 *   <li>Upgrading security level (X25519+ML-KEM-768 → X448+ML-KEM-1024)</li>
 *   <li>Rotating composite to composite with different components</li>
 *   <li>Verifying rotation chains</li>
 * </ul>
 *
 * <h3>Rotation Patterns:</h3>
 * <ol>
 *   <li>Simple RSA → Composite hybrid X25519+ML-KEM-768 (migration to PQC)</li>
 *   <li>X25519+ML-KEM-768 → X448+ML-KEM-1024 (security level upgrade)</li>
 *   <li>Composite with KDF upgrade (HKDF-SHA256 → HKDF-SHA512)</li>
 * </ol>
 *
 * @author ANKATech Solutions Inc.
 * @since 3.0.0
 */
public final class ExampleScenario34 {

    private static final Path TEMP_DIR = Path.of("temp_files");

    private ExampleScenario34() { }

    public static void main(String[] args) {
        try {
            System.out.println("=================================================================");
            System.out.println("  SCENARIO 34: Composite Key Rotation");
            System.out.println("=================================================================\n");

            ensureTempDir(TEMP_DIR);
            java.util.Properties props = loadProperties();
            AuthenticatedSdk sdk = authenticate(props);

            demonstrateSimpleToCompositeRotation(sdk);
            demonstrateCompositeToCompositeRotation(sdk);
            demonstrateKdfUpgrade(sdk);

            System.out.println("\n=================================================================");
            System.out.println("  ALL ROTATION PATTERNS DEMONSTRATED");
            System.out.println("=================================================================");

        } catch (Exception e) {
            fatal("Scenario 34 failed", e);
        }
    }

    /**
     * Pattern 1: Rotate simple RSA key to composite hybrid.
     */
    private static void demonstrateSimpleToCompositeRotation(AuthenticatedSdk sdk) throws Exception {
        System.out.println("[1/3] ROTATE SIMPLE → COMPOSITE (Classical → Hybrid)\n");

        // Create simple RSA key
        String simpleKid = "simple_rsa_" + System.currentTimeMillis();
        KeyRequest simpleRequest = new KeyRequest()
            .kid(simpleKid)
            .kty("RSA")
            .alg("RSA-4096");

        KeyMetadata simpleKey = sdk.generateKey(simpleRequest);
        System.out.println("      Original key created: " + simpleKey.getKid());
        System.out.println("      Type: " + simpleKey.getKty());
        System.out.println("      Algorithm: " + simpleKey.getAlg());
        System.out.println();

        // Rotate to composite hybrid
        // Note: X25519 (128-bit) pairs with ML-KEM-768 (Level 3) for balanced security
        // For ML-KEM-1024 (Level 5), use X448 or P-384/P-521
        RotationMaterialSpec compositeMaterial = new RotationMaterialSpec()
            .kty("COMPOSITE_KEM_COMBINE")
            .alg("X25519+ML-KEM-768")
            .kdf("HKDF-SHA256");

        System.out.println("      Rotating material to hybrid (X25519+ML-KEM-768), same kid...");
        System.out.println("      Note: RSA supports sign/verify, COMPOSITE_KEM_COMBINE does not");
        System.out.println("            Acknowledging capability reduction...");

        // Must acknowledge capability reduction: RSA has sign/verify, KEM does not
        KeyMetadata rotated = sdk.rotateKey(simpleKid, compositeMaterial, true);

        System.out.println("\n      ✅ Rotation complete");
        System.out.println("      KID (unchanged): " + rotated.getKid());
        System.out.println("      New Type: " + rotated.getKty());
        System.out.println("      New Algorithm: " + rotated.getAlg());
        System.out.println("      KDF: " + rotated.getKdf());
        System.out.println("      Capability reduction: Lost sign/verify operations");
        System.out.println("      Migration: Classical RSA → Quantum-resistant hybrid\n");
    }

    /**
     * Pattern 2: Upgrade PQC component in composite key.
     */
    private static void demonstrateCompositeToCompositeRotation(AuthenticatedSdk sdk) throws Exception {
        System.out.println("[2/3] ROTATE COMPOSITE → COMPOSITE (Security Level Upgrade)\n");

        // Create Level 3 composite key (X25519 = 128-bit, ML-KEM-768 = Level 3)
        String level3Kid = "hybrid_level3_" + System.currentTimeMillis();
        KeyRequest level3Request = new KeyRequest()
            .kid(level3Kid)
            .kty("COMPOSITE_KEM_COMBINE")
            .alg("X25519+ML-KEM-768");      // Level 3 PQC

        KeyMetadata level3Key = sdk.generateKey(level3Request);
        System.out.println("      Original key: " + level3Key.getKid());
        System.out.println("      Algorithm: " + level3Key.getAlg() + " (X25519 + ML-KEM Level 3)");
        System.out.println();

        // Upgrade material to Level 5 (kid unchanged)
        // X448+ML-KEM-1024 provides balanced Level 5 security (both components ~192-256 bit)
        RotationMaterialSpec level5Material = new RotationMaterialSpec()
            .kty("COMPOSITE_KEM_COMBINE")
            .alg("X448+ML-KEM-1024");       // Upgraded both classical and PQC

        System.out.println("      Upgrading to Level 5...");
        KeyMetadata upgraded = sdk.rotateKey(level3Kid, level5Material, false);

        System.out.println("\n      ✅ Security upgrade complete");
        System.out.println("      KID (unchanged): " + upgraded.getKid());
        System.out.println("      New Algorithm: " + upgraded.getAlg() + " (Level 5)");
        System.out.println("      Upgrade: X25519+ML-KEM-768 → X448+ML-KEM-1024\n");
    }

    /**
     * Pattern 3: Upgrade KDF in composite key.
     */
    private static void demonstrateKdfUpgrade(AuthenticatedSdk sdk) throws Exception {
        System.out.println("[3/3] ROTATE WITH KDF UPGRADE (SHA256 → SHA512)\n");

        // Create key with HKDF-SHA256
        String sha256Kid = "hybrid_sha256_" + System.currentTimeMillis();
        KeyRequest sha256Request = new KeyRequest()
            .kid(sha256Kid)
            .kty("COMPOSITE_KEM_COMBINE")
            .alg("X25519+ML-KEM-768")
            .kdf("HKDF-SHA256");

        KeyMetadata sha256Key = sdk.generateKey(sha256Request);
        System.out.println("      Original key: " + sha256Key.getKid());
        System.out.println("      KDF: " + sha256Key.getKdf());
        System.out.println();

        // Rotate material with stronger KDF (kid unchanged)
        RotationMaterialSpec sha512Material = new RotationMaterialSpec()
            .kty("COMPOSITE_KEM_COMBINE")
            .alg("X25519+ML-KEM-768")
            .kdf("HKDF-SHA512");            // Stronger KDF

        System.out.println("      Upgrading KDF...");
        KeyMetadata upgraded = sdk.rotateKey(sha256Kid, sha512Material, false);

        System.out.println("\n      ✅ KDF upgrade complete");
        System.out.println("      KID (unchanged): " + upgraded.getKid());
        System.out.println("      New KDF: " + upgraded.getKdf());
        System.out.println("      Upgrade: HKDF-SHA256 → HKDF-SHA512\n");
    }
}

Running This Example

Prerequisites

  1. AnkaSecure SDK 3.0.0 or higher
  2. Java 17+
  3. Valid cli.properties with credentials
  4. Running AnkaSecure Core API instance

Compilation and Execution

# Compile example
javac -cp "ankasecure-sdk-3.0.0.jar:lib/*" ExampleScenario34.java ExampleUtil.java

# Run example
java -cp ".:ankasecure-sdk-3.0.0.jar:lib/*" \
  -Dcli.config=cli.properties \
  co.ankatech.ankasecure.sdk.examples.ExampleScenario34

# Or via Maven
cd ankasecure-sdk
mvn exec:java -Dexec.mainClass="co.ankatech.ankasecure.sdk.examples.ExampleScenario34"

Expected Output

===== SCENARIO 34: COMPOSITE KEY ROTATION =====
Purpose: Rotate hybrid quantum-resistant keys
Pattern: Generate Composite → Rotate → Verify Chain

╔═══════════════════════════════════════════╗
║  PHASE 1: GENERATE ORIGINAL COMPOSITE    ║
╚═══════════════════════════════════════════╝

[1/3] Building composite key specification...
      KID: sc34_hybrid_v1_1735396800000
      Mode: COMPOSITE_KEM_COMBINE
      Components:
        • Classical: X25519 (Curve25519 ECDH, Level 3)
        • PQC: ML-KEM-768 (NIST FIPS 203, Level 3)
      KDF: HKDF-SHA256

[2/3] Generating composite key on server...
      ✅ Composite key generated
      Algorithm: X25519+ML-KEM-768
      Status: ACTIVE
      Usage count: 0

╔═══════════════════════════════════════════╗
║  PHASE 2: ROTATE MATERIAL VERSION        ║
╚═══════════════════════════════════════════╝

[3/5] Building new material specification...
      Stable KID: sc34_hybrid_1735396800000 (unchanged)
      Current material version: 1 (PRIMARY)
      ⚠️ IMPORTANT:
         • Mode MUST match (COMPOSITE_KEM_COMBINE)
         • Algorithm CAN change within the alias purpose (migration)
         • Cannot change to COMPOSITE_SIGNATURE (different purpose)
         • Cannot downgrade to SIMPLE

[4/5] Executing composite key rotation...
      Calling: sdk.rotateKey()
      ✅ Rotation successful
      Stable KID: sc34_hybrid_1735396800000 (unchanged)
      New material version: 2 (PRIMARY)

╔═══════════════════════════════════════════╗
║  PHASE 3: VERIFY MATERIAL VERSION        ║
╚═══════════════════════════════════════════╝

[5/7] Retrieving key metadata...
      Key:
         • KID: sc34_hybrid_1735396800000 (unchanged)
         • Status: ACTIVE
         • Primary material version: 2

[6/7] Inspecting material lineage...
      Material versions under the same kid:
         • v1 → RETIRED (historical decrypt preserved)
         • v2 → PRIMARY (active)

[7/7] Verifying stable-kid invariant...
      ✅ Verified:
         • The kid never changed across rotation
         • Previous version v1 → RETIRED
         • New version v2 → PRIMARY
         • There is NO nextKid / previousKid

╔═══════════════════════════════════════════╗
║  PHASE 4: TRANSPARENT ENCRYPTION         ║
╚═══════════════════════════════════════════╝

[8/10] Creating test plaintext...
       File: temp_files/sc34_plaintext.txt
       Size: 45 bytes

[9/10] Encrypting with the (unchanged) KID...
       KID: sc34_hybrid_1735396800000
       ✅ Encryption successful
       Material used: version 2 (PRIMARY)
       Algorithm: COMPOSITE-KEM-COMBINE

[10/10] Analyzing transparent material resolution...
       ✅ Transparent resolution verified:
          • Client always references the same kid
          • Encrypt resolves to the PRIMARY material (v2)
          • Rotation transparent to client
          • No code changes required

       Security guarantee:
       • Encryption uses the NEW PRIMARY material
       • Historical data decrypts with the RETIRED material
       • Zero-downtime rotation achieved

✅ SCENARIO 34 SUCCESSFUL
   - Stable KID: sc34_hybrid_1735396800000 (unchanged)
   - Material version: 1 (RETIRED) → 2 (PRIMARY)
   - Quantum resistance: maintained

===== SCENARIO 34 END =====

Key Concepts

1. Composite Key Rotation Rules

Immutable Requirements:

  • Stable KID: the kid is FIXED across rotation — rotation never mints a new identifier
  • Purpose Preservation: a COMPOSITE_KEM_COMBINE (encrypt/decrypt) key cannot become COMPOSITE_SIGNATURE (sign/verify) — the alias purpose is invariant
  • No SIMPLE downgrade: a COMPOSITE key cannot rotate to a SIMPLE key

Mutable Across a Rotation (new material version):

  • The algorithm — a migration (e.g. X25519+ML-KEM-768X448+ML-KEM-1024) is just a new material version with a different algorithm inside the same purpose
  • maxUsageLimit, softUsageLimit
  • expiresAt, softLimitExpiration
  • exportable flag
  • keyOps (subject to the alias purpose)

The kid is not part of the request — it is taken from the path and never changes.

Blocked Scenarios:

// Cannot change purpose (KEM ↔ SIGNATURE)
Current:     COMPOSITE_KEM_COMBINE
newMaterial: COMPOSITE_SIGNATURE
Result: InvalidInputException("Composite mode mismatch")

// Cannot downgrade to SIMPLE
Current:     COMPOSITE (X25519 + ML-KEM-768)
newMaterial: SIMPLE (ML-KEM-1024)
Result: InvalidInputException("Cannot rotate COMPOSITE key to SIMPLE key")

// Supplying a kid in newMaterial is rejected (kid is fixed, taken from the path)
newMaterial: { "kid": "...", ... }
Result: HTTP 400 invalid-rotation-request

2. Material Version Lineage

Single identifier, versioned material:

Stable KID "hybrid-key" (status: ACTIVE, primary_material_version: 2)
  ├─ material v1 → RETIRED  (historical decrypt/verify preserved)
  └─ material v2 → PRIMARY  (active for encrypt/sign)

There is no nextKid / previousKid — the identifier never changes; the wire-visible version is the ank_kv JOSE protected-header claim.

Audit Trail:

  • Compliance: Proves the material version advanced (every rotation records materialVersion)
  • Forensics: Traces historical material versions under the same Stable KID
  • Migration: Identifies the algorithm lineage across versions

3. Transparent Redirection

Zero-Downtime Rotation:

When a client encrypts with a kid whose material has been rotated:

Client Request:
  POST /api/crypto/encrypt
  { "kid": "hybrid-key", "data": "..." }

Server Behavior:
  1. Resolve "hybrid-key" → alias ACTIVE, primary_material_version = 2
  2. Encrypt using material v2 (PRIMARY)
  3. Token protected header carries ank_kv = 2
  4. Return: keyRequested="hybrid-key", materialVersion=2

Client Result:
  ✅ Encryption successful
  ℹ️ The kid never changed — the platform resolved the current PRIMARY material

Benefits:

  • No client code changes required (the kid never changes)
  • Historical data still decrypts with the RETIRED material version
  • New data encrypts with the new PRIMARY material version
  • Gradual migration without forced re-encryption

4. Quantum Resistance Continuity

Security Guarantee During Rotation:

Before Rotation:
  Key: hybrid-v1 (X25519 + ML-KEM-768)
  Security: Quantum-resistant (HNDR protected)

During Rotation:
  Old Key: hybrid-v1 (ROTATED, decrypt-only)
  New Key: hybrid-v2 (ACTIVE, encrypt+decrypt)
  Security: BOTH quantum-resistant

After Rotation:
  New Key: hybrid-v2 (X25519 + ML-KEM-768)
  Security: Quantum-resistant maintained
  Historical Data: Protected by hybrid-v1

No Security Gap: Quantum resistance maintained throughout rotation lifecycle.


  • Flow 23 - Simple key rotation (RSA → ML-KEM)
  • Flow 29 - Composite key generation and usage
  • Flow 30 - Regulatory compliance templates
  • Flow 17 - Key lifecycle and revocation

Flow: 34

Complexity: Intermediate

Estimated Time: 8 minutes