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

/*
 * 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
 */
/* *****************************************************************************
 * FILE: ExampleScenario21.java
 * ****************************************************************************/
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 co.ankatech.ankasecure.sdk.examples.ExampleUtil.*;
import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * <h2>Scenario&nbsp;21 – Compact-Token Rotation (RSA-2048 ➜ ML-KEM-1024)</h2>
 *
 * <p>This example migrates a Compact&nbsp;JWE from a classical RSA-2048 key to
 * a post-quantum ML-KEM-1024 key <em>entirely in memory</em>. The original
 * plaintext is never exposed to the client.</p>
 *
 * <h3>Prerequisites</h3>
 * <ul>
 *   <li>A valid encrypted CLI initialisation file (credentials).</li>
 *   <li>An access-token with the scopes:
 *       <code>key_management.generateKey</code>,
 *       <code>secure.encrypt</code>,
 *       <code>secure.reencrypt</code>.</li>
 * </ul>
 *
 * @author ANKATech – Security Engineering
 * @since 2.1.0
 */
public final class ExampleScenario21 {

    /* Dynamically generated key IDs ensure isolation across runs. */
    private static final String SRC_KID = "sc21_RSA2048_"   + System.currentTimeMillis();
    private static final String DST_KID = "sc21_MLKEM1024_" + System.currentTimeMillis();

    /* ====================================================================== */
    /** Entry-point. */
    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 = loadProperties();
            AnkaSecureSdk sdk = authenticate(props);
            runScenario(sdk);
        } catch (Exception ex) {
            fatal("Scenario 21 failed", ex);
        }

        System.out.println("===== SCENARIO 21 END =====");
    }

    /* ====================================================================== */
    /** Executes key creation, encryption, and token rotation. */
    private static void runScenario(final AnkaSecureSdk sdk)
            throws AnkaSecureSdkException {

        /* 1 ── RSA-2048 key (decrypt-capable) -------------------------- */
        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 (encrypt-capable) ---------------------- */
        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!");
    }

    /** Extracts and Base64URL-decodes the protected-header segment. */
    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)";
        }
    }
    /** Utility class – no instantiation. */
    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 ANKATech Solutions INC. All rights reserved.