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:
- Generate original composite key (X25519 + ML-KEM-768, COMPOSITE_KEM_COMBINE)
- Rotate the key material to a new version using
rotateKey()— the Stable KID stays the same - Verify the kid is unchanged and the material version advanced (previous version
RETIRED, new versionPRIMARY) - Verify the new material is
PRIMARYunder the same Stable KID - Demonstrate encryption transparently uses the new
PRIMARYmaterial
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
- 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: 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 becomeCOMPOSITE_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-768→X448+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.
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