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.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
- 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