Skip to content

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:

  1. Generate an RSA-2048 key (decrypt-capable).

  2. Generate an ML-KEM-1024 key (encrypt-capable).

  3. Encrypt a UTF-8 message → Compact JWE under the RSA key.

  4. Re-encrypt that token so the payload is now protected by the ML-KEM key (server-side, zero plaintext exposure).

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

mvn -q compile exec:java\
  -Dexec.mainClass="co.ankatech.ankasecure.sdk.examples.ExampleScenario21"

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.