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:
- Generate original composite key (X25519 + ML-KEM-768, HYBRID_KEM_COMBINE)
- Rotate to successor with same mode and algorithms using
createCompositeRotation() - Verify bidirectional rotation chain (original.nextKid → successor, successor.previousKid → original)
- Verify original key status = ROTATED
- 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.AuthenticatedSdk;
import co.ankatech.ankasecure.sdk.exception.AnkaSecureSdkException;
import co.ankatech.ankasecure.sdk.model.*;
import co.ankatech.ankasecure.sdk.util.FileIO;
import java.nio.file.Files;
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).
*
* <p>Demonstrates quantum-resistant key rotation maintaining mode and algorithms:</p>
* <ol>
* <li>Generate original composite key (X25519 + ML-KEM-768)</li>
* <li>Rotate to successor with same mode and algorithms</li>
* <li>Verify rotation chain (originalKey.nextKid → successor)</li>
* <li>Verify old key status = ROTATED</li>
* <li>Demonstrate encryption uses successor transparently</li>
* </ol>
*
* <p><strong>Key Validation Rules:</strong></p>
* <ul>
* <li>Same mode required (HYBRID_KEM_COMBINE → HYBRID_KEM_COMBINE)</li>
* <li>Same algorithms required (X25519 + ML-KEM-768 → X25519 + ML-KEM-768)</li>
* <li>Atomic rotation (all components together)</li>
* <li>Mode change blocked (HYBRID_KEM → DUALSIGN)</li>
* <li>Algorithm change blocked (X25519 → RSA-3072)</li>
* <li>Downgrade blocked (COMPOSITE → SIMPLE)</li>
* </ul>
*
* @author ANKATech Solutions Inc.
* @since 3.0.0
* @see ExampleUtil
* @see AuthenticatedSdk
*/
public final class ExampleScenario34 {
private static final Path TEMP_DIR = Path.of("temp_files");
/** No instantiation — this class only exposes {@link #main(String[])}. */
private ExampleScenario34() { }
/**
* Runs the composite key rotation scenario.
*
* <p>Loads CLI properties, authenticates against ANKASecure©,
* and delegates to the scenario logic. On any unrecoverable error
* the JVM terminates via
* {@link ExampleUtil#fatal(String, Throwable)}.</p>
*
* @param args command-line arguments (ignored)
*/
public static void main(String[] args) {
System.out.println("===== SCENARIO 34: COMPOSITE KEY ROTATION =====");
System.out.println("Purpose: Rotate hybrid quantum-resistant keys");
System.out.println("Pattern: Generate Composite → Rotate → Verify Chain");
System.out.println();
try {
ensureTempDir(TEMP_DIR);
Properties props = loadProperties();
AuthenticatedSdk sdk = authenticate(props);
runScenario(sdk);
System.out.println("===== SCENARIO 34 END =====");
} catch (Exception ex) {
fatal("Scenario 34 failed", ex);
}
}
/* ====================================================================== */
private static void runScenario(AuthenticatedSdk sdk) throws Exception {
// PHASE 1: Generate Original Composite Key
System.out.println("╔═══════════════════════════════════════════╗");
System.out.println("║ PHASE 1: GENERATE ORIGINAL COMPOSITE ║");
System.out.println("╚═══════════════════════════════════════════╝");
String originalKid = generateOriginalCompositeKey(sdk);
// PHASE 2: Rotate to Successor
System.out.println("╔═══════════════════════════════════════════╗");
System.out.println("║ PHASE 2: ROTATE TO SUCCESSOR ║");
System.out.println("╚═══════════════════════════════════════════╝");
String successorKid = rotateToSuccessor(sdk, originalKid);
// PHASE 3: Verify Rotation Chain
System.out.println("╔═══════════════════════════════════════════╗");
System.out.println("║ PHASE 3: VERIFY ROTATION CHAIN ║");
System.out.println("╚═══════════════════════════════════════════╝");
verifyRotationChain(sdk, originalKid, successorKid);
// PHASE 4: Demonstrate Transparent Encryption
System.out.println("╔═══════════════════════════════════════════╗");
System.out.println("║ PHASE 4: TRANSPARENT ENCRYPTION ║");
System.out.println("╚═══════════════════════════════════════════╝");
demonstrateTransparentEncryption(sdk, originalKid, successorKid);
// Final Summary
System.out.println();
System.out.println("✅ SCENARIO 34 SUCCESSFUL");
System.out.println(" - Original key: " + originalKid + " (ROTATED)");
System.out.println(" - Successor key: " + successorKid + " (ACTIVE)");
System.out.println(" - Rotation chain: verified");
System.out.println(" - Quantum resistance: maintained");
}
private static String generateOriginalCompositeKey(AuthenticatedSdk sdk) throws Exception {
String kid = "sc34_hybrid_v1_" + System.currentTimeMillis();
System.out.println("[1/3] Building composite key specification...");
System.out.println(" KID: " + kid);
System.out.println(" Mode: HYBRID_KEM_COMBINE");
System.out.println(" Components:");
System.out.println(" • Classical: X25519 (Curve25519 ECDH, Level 3)");
System.out.println(" • PQC: ML-KEM-768 (NIST FIPS 203, Level 3)");
System.out.println(" KDF: HKDF-SHA256");
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);
System.out.println();
System.out.println("[2/3] Generating composite key on server...");
KeyGenerationSummarySpec result = sdk.generateCompositeKey(spec);
System.out.println(" ✅ Composite key generated");
System.out.println(" Algorithm: " + result.getAlg());
System.out.println(" Status: " + result.getStatus());
System.out.println(" Usage count: " + result.getUsageCount());
System.out.println();
return kid;
}
private static String rotateToSuccessor(AuthenticatedSdk sdk, String originalKid) throws Exception {
String successorKid = "sc34_hybrid_v2_" + System.currentTimeMillis();
System.out.println("[3/5] Building successor specification...");
System.out.println(" Original KID: " + originalKid);
System.out.println(" Successor KID: " + successorKid);
System.out.println(" ⚠️ IMPORTANT:");
System.out.println(" • Mode MUST match (HYBRID_KEM_COMBINE)");
System.out.println(" • Algorithms MUST match (X25519 + ML-KEM-768)");
System.out.println(" • Cannot change to DUALSIGN");
System.out.println(" • Cannot downgrade to SIMPLE");
GenerateCompositeKeySpec successorSpec = new GenerateCompositeKeySpec()
.setKid(successorKid)
.setMode(GenerateCompositeKeySpec.Mode.HYBRID_KEM_COMBINE) // Same mode
.addComponent(ComponentSpec.classical(ClassicalCompositeAlgorithm.X25519)) // Same algorithm
.addComponent(ComponentSpec.pqc(PqcCompositeAlgorithm.ML_KEM_768)) // Same algorithm
.setKdf(Kdf.HKDF_SHA256)
.setMaxUsageLimit(2000000); // Can change lifecycle settings
System.out.println();
System.out.println("[4/5] Executing composite key rotation...");
System.out.println(" Calling: sdk.rotateCompositeKey()");
KeyGenerationSummarySpec successor = sdk.rotateCompositeKey(originalKid, successorSpec);
System.out.println(" ✅ Rotation successful");
System.out.println(" Successor KID: " + successor.getKid());
System.out.println(" Successor Status: " + successor.getStatus());
System.out.println();
return successorKid;
}
private static void verifyRotationChain(AuthenticatedSdk sdk, String originalKid, String successorKid)
throws Exception {
System.out.println("[5/7] Retrieving original key metadata...");
// Note: In v3.0.0, getKeyMetadata() returns KeyMetadata (custom model)
// which may not have all fields populated from KeySummaryResponse
// For full metadata with rotation chain, use exportKey() instead
ExportedKeySpec originalMeta = sdk.exportKey(originalKid);
System.out.println(" Original key:");
System.out.println(" • KID: " + originalMeta.getKid());
System.out.println(" • Status: " + originalMeta.getStatus()); // Should be ROTATED
System.out.println(" • Next KID: " + originalMeta.getNextKid()); // Should point to successor
System.out.println();
System.out.println("[6/7] Retrieving successor key metadata...");
ExportedKeySpec successorMeta = sdk.exportKey(successorKid);
System.out.println(" Successor key:");
System.out.println(" • KID: " + successorMeta.getKid());
System.out.println(" • Status: " + successorMeta.getStatus()); // Should be ACTIVE
System.out.println(" • Previous KID: " + successorMeta.getPreviousKid()); // Should point to original
System.out.println();
System.out.println("[7/7] Verifying bidirectional rotation chain...");
if ("ROTATED".equals(originalMeta.getStatus()) &&
successorKid.equals(originalMeta.getNextKid()) &&
originalKid.equals(successorMeta.getPreviousKid())) {
System.out.println(" ✅ Rotation chain verified:");
System.out.println(" • Original → ROTATED status");
System.out.println(" • Original.nextKid → Successor");
System.out.println(" • Successor.previousKid → Original");
System.out.println(" • Bidirectional linkage: VALID");
} else {
System.out.println(" ❌ Rotation chain verification failed");
}
System.out.println();
}
private static void demonstrateTransparentEncryption(AuthenticatedSdk sdk,
String originalKid,
String successorKid) throws Exception {
System.out.println("[8/10] Creating test plaintext...");
String plaintext = "Quantum-resistant data requiring key rotation";
Path plaintextFile = TEMP_DIR.resolve("sc34_plaintext.txt");
Path encryptedFile = TEMP_DIR.resolve("sc34_encrypted.jwe");
FileIO.writeUtf8(plaintextFile, plaintext);
System.out.println(" File: " + plaintextFile);
System.out.println(" Size: " + plaintext.length() + " bytes");
System.out.println();
System.out.println("[9/10] Encrypting with ORIGINAL KID (should use successor)...");
System.out.println(" Requested KID: " + originalKid + " (ROTATED)");
EncryptResult result = sdk.encryptFileStream(originalKid, plaintextFile, encryptedFile);
System.out.println(" ✅ Encryption successful");
System.out.println(" Key requested: " + result.getKeyRequested());
System.out.println(" Key actually used: " + result.getActualKeyUsed());
System.out.println(" Algorithm: " + result.getAlgorithmUsed());
System.out.println();
System.out.println("[10/10] Analyzing transparent redirection...");
if (originalKid.equals(result.getKeyRequested()) &&
successorKid.equals(result.getActualKeyUsed())) {
System.out.println(" ✅ Transparent redirection verified:");
System.out.println(" • Client requested: " + originalKid);
System.out.println(" • Server used: " + successorKid);
System.out.println(" • Rotation transparent to client");
System.out.println(" • No code changes required");
} else {
System.out.println(" ⚠️ Unexpected key usage");
}
System.out.println();
System.out.println(" Security guarantee:");
System.out.println(" • Encryption uses NEW rotated key");
System.out.println(" • Historical data decrypts with OLD key");
System.out.println(" • Zero-downtime rotation achieved");
System.out.println();
}
}
Running This Example
Prerequisites
- AnkaSecure SDK 3.0.0 or higher
- Java 17+
- Valid
cli.propertieswith credentials - 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) |
Related Examples
- 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