Skip to content

Flow 34 – Composite Key Rotation (HYBRID_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 to successor
  • POST /api/crypto/encrypt - Encrypt with rotated key (transparent redirection)

Steps:

  1. Generate original composite key (X25519 + ML-KEM-768, HYBRID_KEM_COMBINE)
  2. Rotate to successor with same mode and algorithms using createCompositeRotation()
  3. Verify bidirectional rotation chain (original.nextKid → successor, successor.previousKid → original)
  4. Verify original key status = ROTATED
  5. Demonstrate encryption transparently uses successor

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

/*
 * Copyright 2025 ANKATech Solutions Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * SPDX-License-Identifier: Apache-2.0
 */
package co.ankatech.ankasecure.sdk.examples;

import co.ankatech.ankasecure.sdk.AnkaSecureSdk;
import co.ankatech.ankasecure.sdk.exception.AnkaSecureSdkException;
import co.ankatech.ankasecure.sdk.model.*;
import co.ankatech.ankasecure.sdk.util.FileIO;

import java.nio.file.Path;
import java.util.Properties;

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

/**
 * Scenario 34 – Composite Key Rotation (HYBRID_KEM_COMBINE)
 * Demonstrates quantum-resistant key rotation with validation
 */
public final class ExampleScenario34 {

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

    public static void main(String[] args) {
        System.out.println("===== SCENARIO 34: COMPOSITE KEY ROTATION =====");

        try {
            ensureTempDir(TEMP_DIR);
            Properties props = loadProperties();
            AnkaSecureSdk sdk = authenticate(props);

            runScenario(sdk);

            System.out.println("===== SCENARIO 34 END =====");
        } catch (Exception ex) {
            fatal("Scenario 34 failed", ex);
        }
    }

    private static void runScenario(AnkaSecureSdk sdk) throws Exception {

        // PHASE 1: Generate Original Composite Key
        String originalKid = generateOriginalCompositeKey(sdk);

        // PHASE 2: Rotate to Successor
        String successorKid = rotateToSuccessor(sdk, originalKid);

        // PHASE 3: Verify Rotation Chain
        verifyRotationChain(sdk, originalKid, successorKid);

        // PHASE 4: Demonstrate Transparent Encryption
        demonstrateTransparentEncryption(sdk, originalKid, successorKid);
    }

    private static String generateOriginalCompositeKey(AnkaSecureSdk sdk) throws Exception {
        String kid = "sc34_hybrid_v1_" + System.currentTimeMillis();

        GenerateCompositeKeySpec spec = new GenerateCompositeKeySpec()
                .setKid(kid)
                .setMode(GenerateCompositeKeySpec.Mode.HYBRID_KEM_COMBINE)
                .addComponent(ComponentSpec.classical(ClassicalCompositeAlgorithm.X25519))
                .addComponent(ComponentSpec.pqc(PqcCompositeAlgorithm.ML_KEM_768))
                .setKdf(Kdf.HKDF_SHA256)
                .setMaxUsageLimit(1000000)
                .setExportable(true);

        ExportedKeySpec result = sdk.generateCompositeKey(spec);

        System.out.println("✅ Original composite key generated");
        System.out.println("   KID: " + kid);
        System.out.println("   Algorithm: " + result.getAlg());
        System.out.println("   Status: " + result.getStatus());

        return kid;
    }

    private static String rotateToSuccessor(AnkaSecureSdk sdk, String originalKid) throws Exception {
        String successorKid = "sc34_hybrid_v2_" + System.currentTimeMillis();

        // Successor must maintain same mode and algorithms
        GenerateCompositeKeySpec successorSpec = new GenerateCompositeKeySpec()
                .setKid(successorKid)
                .setMode(GenerateCompositeKeySpec.Mode.HYBRID_KEM_COMBINE)  // MUST match
                .addComponent(ComponentSpec.classical(ClassicalCompositeAlgorithm.X25519))  // MUST match
                .addComponent(ComponentSpec.pqc(PqcCompositeAlgorithm.ML_KEM_768))  // MUST match
                .setKdf(Kdf.HKDF_SHA256)
                .setMaxUsageLimit(2000000);  // Can change

        System.out.println("Rotating composite key...");
        ExportedKeySpec successor = sdk.createCompositeRotation(originalKid, successorSpec);

        System.out.println("✅ Rotation successful");
        System.out.println("   Successor KID: " + successor.getKid());
        System.out.println("   Previous KID: " + successor.getPreviousKid());

        return successorKid;
    }

    private static void verifyRotationChain(AnkaSecureSdk sdk, String originalKid, String successorKid)
            throws Exception {

        // Get metadata for both keys
        ExportedKeySpec originalMeta = sdk.exportKey(originalKid);
        ExportedKeySpec successorMeta = sdk.exportKey(successorKid);

        System.out.println("✅ Rotation chain verified:");
        System.out.println("   Original: " + originalKid);
        System.out.println("      • Status: " + originalMeta.getStatus());
        System.out.println("      • Next KID: " + originalMeta.getNextKid());
        System.out.println("   Successor: " + successorKid);
        System.out.println("      • Status: " + successorMeta.getStatus());
        System.out.println("      • Previous KID: " + successorMeta.getPreviousKid());
    }

    private static void demonstrateTransparentEncryption(AnkaSecureSdk sdk,
                                                         String originalKid,
                                                         String successorKid) throws Exception {

        String plaintext = "Data requiring quantum-resistant key rotation";
        Path plaintextFile = TEMP_DIR.resolve("sc34_plaintext.txt");
        Path encryptedFile = TEMP_DIR.resolve("sc34_encrypted.jwe");

        FileIO.writeUtf8(plaintextFile, plaintext);

        // Encrypt with original KID (server redirects to successor)
        EncryptResult result = sdk.encryptFileStream(originalKid, plaintextFile, encryptedFile);

        System.out.println("✅ Transparent encryption:");
        System.out.println("   Requested: " + originalKid);
        System.out.println("   Actually used: " + result.getActualKeyUsed());
        System.out.println("   Zero-downtime rotation achieved");
    }
}

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: HYBRID_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 TO SUCCESSOR            ║
╚═══════════════════════════════════════════╝

[3/5] Building successor specification...
      Original KID: sc34_hybrid_v1_1735396800000
      Successor KID: sc34_hybrid_v2_1735396800001
      ⚠️ IMPORTANT:
         • Mode MUST match (HYBRID_KEM_COMBINE)
         • Algorithms MUST match (X25519 + ML-KEM-768)
         • Cannot change to DUALSIGN
         • Cannot downgrade to SIMPLE

[4/5] Executing composite key rotation...
      Calling: sdk.createCompositeRotation()
      ✅ Rotation successful
      Successor KID: sc34_hybrid_v2_1735396800001
      Successor Status: ACTIVE

╔═══════════════════════════════════════════╗
║  PHASE 3: VERIFY ROTATION CHAIN          ║
╚═══════════════════════════════════════════╝

[5/7] Retrieving original key metadata...
      Original key:
         • KID: sc34_hybrid_v1_1735396800000
         • Status: ROTATED
         • Next KID: sc34_hybrid_v2_1735396800001

[6/7] Retrieving successor key metadata...
      Successor key:
         • KID: sc34_hybrid_v2_1735396800001
         • Status: ACTIVE
         • Previous KID: sc34_hybrid_v1_1735396800000

[7/7] Verifying bidirectional rotation chain...
      ✅ Rotation chain verified:
         • Original → ROTATED status
         • Original.nextKid → Successor
         • Successor.previousKid → Original
         • Bidirectional linkage: VALID

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

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

[9/10] Encrypting with ORIGINAL KID (should use successor)...
       Requested KID: sc34_hybrid_v1_1735396800000 (ROTATED)
       ✅ Encryption successful
       Key requested: sc34_hybrid_v1_1735396800000
       Key actually used: sc34_hybrid_v2_1735396800001
       Algorithm: HYBRID-KEM-COMBINE

[10/10] Analyzing transparent redirection...
       ✅ Transparent redirection verified:
          • Client requested: sc34_hybrid_v1_1735396800000
          • Server used: sc34_hybrid_v2_1735396800001
          • Rotation transparent to client
          • No code changes required

       Security guarantee:
       • Encryption uses NEW rotated key
       • Historical data decrypts with OLD key
       • Zero-downtime rotation achieved

✅ SCENARIO 34 SUCCESSFUL
   - Original key: sc34_hybrid_v1_1735396800000 (ROTATED)
   - Successor key: sc34_hybrid_v2_1735396800001 (ACTIVE)
   - Rotation chain: verified
   - Quantum resistance: maintained

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

Key Concepts

1. Composite Key Rotation Rules

Immutable Requirements:

  • Mode Preservation: HYBRID_KEM_COMBINE must remain HYBRID_KEM_COMBINE
  • Algorithm Preservation: Component algorithms must match exactly
  • Classical at index 0: X25519 → X25519
  • PQC at index 1: ML-KEM-768 → ML-KEM-768
  • Component Count: Must maintain same number of components (2)

Mutable Settings:

  • Can change: kid (successor identifier)
  • Can change: maxUsageLimit, softUsageLimit
  • Can change: expiresAt, softLimitExpiration
  • Can change: exportable flag
  • Can change: keyOps (if subset of original)

Blocked Scenarios:

// Cannot change mode
Original: HYBRID_KEM_COMBINE
Successor: DUALSIGN
Result: InvalidInputException("Composite mode mismatch")

// Cannot change algorithms
Original: [X25519, ML-KEM-768]
Successor: [RSA-3072, ML-KEM-768]
Result: InvalidInputException("Component algorithm mismatch at index 0")

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

2. Rotation Chain Tracking

Bidirectional Linkage:

Original Key (ROTATED):
  ├─ kid: "hybrid-v1"
  ├─ status: ROTATED
  ├─ nextKid: "hybrid-v2"  ← Points forward
  └─ previousKid: null

Successor Key (ACTIVE):
  ├─ kid: "hybrid-v2"
  ├─ status: ACTIVE
  ├─ nextKid: null
  └─ previousKid: "hybrid-v1"  ← Points backward

Audit Trail:

  • Compliance: Proves key rotation occurred
  • Forensics: Traces historical encryption keys
  • Migration: Identifies rotation lineage

3. Transparent Redirection

Zero-Downtime Rotation:

When a client encrypts with a ROTATED key identifier:

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

Server Behavior:
  1. Lookup "hybrid-v1" → status = ROTATED
  2. Follow nextKid → "hybrid-v2"
  3. Encrypt using "hybrid-v2" (successor)
  4. Return: keyRequested="hybrid-v1", actualKeyUsed="hybrid-v2"

Client Result:
  ✅ Encryption successful
  ⚠️ Warning: "Key hybrid-v1 is ROTATED, used successor hybrid-v2"

Benefits:

  • No client code changes required
  • Historical data still decrypts with old key
  • New data encrypts with new key
  • 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.


Comparison: Simple vs Composite Rotation

Simple Key Rotation (Flow 23)

// Flexible - can change algorithm family
sdk.createRotation("rsa-key",
    GenerateKeySpec.builder()
        .kty("ML-KEM")  // Different family OK
        .alg("ML-KEM-768")
        .build()
);

Allowed: - RSA → ML-KEM (algorithm family change) - EC → RSA (within classical) - ML-KEM-512 → ML-KEM-1024 (upgrade)

Composite Key Rotation (Flow 34)

// Strict - must maintain mode and algorithms
sdk.createCompositeRotation("hybrid-key",
    GenerateCompositeKeySpec.builder()
        .mode(HYBRID_KEM_COMBINE)  // Must match
        .addComponent(classical(X25519))  // Must match
        .addComponent(pqc(ML_KEM_768))   // Must match
        .build()
);

Restrictions: - Cannot change mode - Cannot change component algorithms - Cannot change component count - Can change lifecycle settings only

Rationale: Composite keys are atomic units - changing components would create a fundamentally different key, not a rotation.


SDK Methods Used

createCompositeRotation()

Signature:

ExportedKeySpec createCompositeRotation(
    String kid,
    GenerateCompositeKeySpec successor
) throws AnkaSecureSdkException

Validation (Client-Side): - Component count = 2 (1 CLASSICAL + 1 PQC) - Component roles valid - Mode is one of: HYBRID_KEM_COMBINE, DUALSIGN - KDF valid for mode - Verification policy valid for DUALSIGN

Validation (Server-Side): - Original key exists and is ACTIVE - Mode matches original - Algorithms match original (positional) - Component count matches - Policy compliance (minSecurityLevel, allowedKdfs, etc.)


Comparison with Flow 23

Aspect Flow 23 (Simple Rotation) Flow 34 (Composite Rotation)
Key Type SIMPLE (RSA-2048 → ML-KEM-768) COMPOSITE (X25519+ML-KEM-768 → X25519+ML-KEM-768)
SDK Method createRotation() createCompositeRotation()
Algorithm Change Allowed (RSA → ML-KEM) Blocked (must match)
Mode Change N/A (no mode for simple) Blocked (must match)
Lifecycle Changes Allowed Allowed
Validation Standard rotation policy Composite-specific + policy
Quantum Resistance Post-rotation (ML-KEM-768) Maintained (hybrid both keys)

  • 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