Flow 21 --- Compact-Token Rotation (Re-Encrypt)
This scenario migrates a Compact JWE from a classical RSA key to a post-quantum ML-KEM key --- entirely in memory and without exposing the plaintext:
-
Generate an RSA-2048 key (decrypt-capable).
-
Generate an ML-KEM-1024 key (encrypt-capable).
-
Encrypt a UTF-8 message → Compact JWE under the RSA key.
-
Re-encrypt that token so the payload is now protected by the ML-KEM key (server-side, zero plaintext exposure).
-
Compare the protected-header segments and print rich metadata to prove the rotation.
Key points
In-place token upgrade --- perfect for crypto-agility or PQC migrations.
Uses the SDK's
reencrypt()
helper: no manual JWE parsing needed.Works completely in RAM: ideal for micro-services, event handlers, or serverless functions.
Shows how to inspect the Base64URL header to verify the new algorithm/KID.
When to use it
-
Immediate crypto upgrades when a recipient still holds legacy tokens that must be secured with stronger or PQ algorithms.
-
Zero-downtime rotations in pipelines where decrypting and re-encrypting on the client side is impossible or too slow.
-
Regulatory mandates that require transparent key roll-overs without touching the original plaintext.
Shared helper – this code imports the utility class from
example_util.md (configuration, authentication, JSON).
Complete Java implementation
src/main/java/co/ankatech/ankasecure/sdk/examples/ExampleScenario21.java
/* *****************************************************************************
* FILE: ExampleScenario21.java
* Copyright © 2025 Anka Technologies.
* SPDX-License-Identifier: MIT
* ---------------------------------------------------------------------------
* Scenario 21 – Compact-Token Rotation (Re-encrypt)
* ---------------------------------------------------------------------------
* Demonstrates how to:
* * Generate an RSA-2048 key (decrypt-capable) and an ML-KEM-1024 key
* (encrypt-capable).
* * Encrypt a UTF-8 message ➜ Compact JWE under the RSA key.
* * Re-encrypt the Compact JWE in memory so that the payload becomes
* protected by the ML-KEM key – plaintext never leaves the service.
* * Inspect metadata and compare protected-header segments to prove the
* rotation occurred.
*
* Ideal for micro-service / serverless environments that require transparent
* cryptographic agility without persisting any transient artefacts.
* ****************************************************************************/
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.EncryptResult;
import co.ankatech.ankasecure.sdk.model.GenerateKeySpec;
import co.ankatech.ankasecure.sdk.model.ReencryptResult;
import java.util.Base64;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
import static java.nio.charset.StandardCharsets.UTF_8;
/**
* Scenario 21 – Compact-Token Rotation (RSA-2048 ➜ ML-KEM-1024)
*/
public final class ExampleScenario21 {
private static final String SRC_KID = "sc21_RSA2048_" + System.currentTimeMillis();
private static final String DST_KID = "sc21_MLKEM1024_" + System.currentTimeMillis();
public static void main(final String[] args) {
System.out.println("===== SCENARIO 21 START =====");
System.out.println("""
Purpose :
* Rotate a Compact JWE from RSA-2048 to ML-KEM-1024.
Steps :
1) Generate RSA-2048 key (decrypt).
2) Generate ML-KEM-1024 key (encrypt).
3) Encrypt plaintext ➜ Compact JWE (RSA).
4) Re-encrypt JWE ➜ Compact JWE (ML-KEM).
5) Compare headers & print metadata.
--------------------------------------------------------------""");
try {
Properties props = ExampleUtil.loadProperties();
AnkaSecureSdk sdk = ExampleUtil.authenticate(props);
runScenario(sdk);
} catch (Exception ex) {
fatal("Scenario 21 failed", ex);
}
System.out.println("===== SCENARIO 21 END =====");
}
private static void runScenario(final AnkaSecureSdk sdk)
throws AnkaSecureSdkException {
/* 1 ── RSA-2048 key ------------------------------------------- */
sdk.generateKey(new GenerateKeySpec()
.setKid(SRC_KID)
.setKty("RSA")
.setAlg("RSA-2048")
.setKeyOps(List.of("encrypt", "decrypt")));
System.out.println("[1] RSA-2048 key generated -> kid = " + SRC_KID);
/* 2 ── ML-KEM-1024 key --------------------------------------- */
sdk.generateKey(new GenerateKeySpec()
.setKid(DST_KID)
.setKty("ML-KEM")
.setAlg("ML-KEM-1024")
.setKeyOps(List.of("encrypt", "decrypt")));
System.out.println("[2] ML-KEM-1024 key generated -> kid = " + DST_KID);
/* 3 ── Encrypt plaintext ➜ Compact JWE (RSA) ------------------ */
byte[] plaintext = "Compact-token rotation demo".getBytes(UTF_8);
EncryptResult encRes = sdk.encrypt(SRC_KID, plaintext);
String oldJwe = encRes.getJweToken();
System.out.println("[3] Original JWE header = " + protectedHeader(oldJwe));
/* 4 ── Re-encrypt JWE ➜ ML-KEM key ---------------------------- */
ReencryptResult renRes = sdk.reencrypt(DST_KID, oldJwe);
String newJwe = renRes.getJweToken();
System.out.println("[4] Rotated JWE header = " + protectedHeader(newJwe));
printReencryptMeta(renRes);
/* 5 ── Verify header changed ---------------------------------- */
boolean changed = !Objects.equals(
protectedHeader(oldJwe),
protectedHeader(newJwe));
System.out.println(changed
? "[✔] SUCCESS – header changed as expected."
: "[✘] FAILURE – header did not change!");
}
/* --------------------- Diagnostics ------------------------------ */
private static void printReencryptMeta(final ReencryptResult r) {
System.out.println("----- RE-ENCRYPT METADATA -----");
System.out.println("OldKeyUsed : " + nullSafe(r.getOldKeyUsed()));
System.out.println("NewKeyUsed : " + nullSafe(r.getNewKeyUsed()));
System.out.println("NewKeyAlgorithmUsed : " + nullSafe(r.getNewKeyAlgorithmUsed()));
printWarnings(r.getWarnings());
}
private static String protectedHeader(final String compactJwe) {
int dot = compactJwe.indexOf('.');
if (dot <= 0) return "(invalid)";
try {
byte[] decoded = Base64.getUrlDecoder()
.decode(compactJwe.substring(0, dot));
return new String(decoded, UTF_8);
} catch (IllegalArgumentException ex) {
return "(decode error)";
}
}
private static void printWarnings(final List<String> warnings) {
if (warnings != null && !warnings.isEmpty()) {
System.out.println("Warnings :");
warnings.forEach(w -> System.out.println(" * " + w));
}
}
private static String nullSafe(final String s) {
return (s == null || s.isBlank()) ? "(none)" : s;
}
private static void fatal(final String msg, final Throwable t) {
System.err.println(msg);
if (t != null) {
t.printStackTrace(System.err);
}
System.exit(1);
}
private ExampleScenario21() { /* no-instantiation */ }
}
How to run
Console milestones
-
RSA-2048 & ML-KEM-1024 keys generated
-
Original Compact-JWE header (RSA) printed
-
Re-encrypted Compact-JWE header (ML-KEM) printed
-
Re-encrypt metadata shows old/new key IDs and algorithm
-
SUCCESS -- protected-header segment changed
Where next?
© 2025 Anka Technologies. All rights reserved.