Skip to content

Flow 8 -- ML-KEM-768 ➜ ML-KEM-1024 Compact-JWE Re-Encrypt (Non-Streaming)

This scenario walks through a non-streaming, server-side migration from a post-quantum KEM key to a stronger post-quantum KEM key, operating on the pre-provisioned granted cross-kid pair (cli-reencrypt3-source / cli-reencrypt3-target):

  • Resolve the granted ML-KEM-768 source kid (cli-reencrypt3-source).

  • Encrypt a plaintext file → compact JWE (five B64URL segments).

  • Resolve the granted ML-KEM-1024 target kid (cli-reencrypt3-target).

  • Re-encrypt the ciphertext ML-KEM-768 ➜ ML-KEM-1024 without ever touching plaintext.

  • Decrypt the fresh KEM ciphertext.

  • Validate that the recovered bytes equal the original.

Key points

  • Uses encryptFile → reencryptFile → decryptFile helpers -- the full token stays in memory; no multipart uploads.

  • Achieves a zero-plaintext crypto upgrade: conversion happens entirely inside AnkaSecure, not on the client.

  • Proves hybrid migrations are possible even for legacy compact-JWE archives, with rich per-step telemetry.

Prerequisites

The flow examples are data-plane only. They never create, rotate, or delete keys — they resolve pre-provisioned playground keys and operate on them. Before running any flow example you must:

  1. Provision a fresh demo-cli playground. Use the ankasecure-demo-provisioning tool to provision the demo-cli playground. This seeds the cryptographic keys the examples operate on and the cli-reference@demo-cli actor that holds capability grants on them.
  2. Use the emitted cli.properties. The provisioning tool writes a cli.properties file that carries the ankasecure.demo.kids catalogue line — the comma-separated list of provisioned key ids, in YAML file order. The examples load this file to discover which keys exist.
  3. Authenticate as cli-reference@demo-cli. Authenticate the SDK as the cli-reference@demo-cli actor — the all-operations actor of the demo-cli playground — so the resolved keys carry the capability grants each operation needs.

Key selection in the examples is by algorithm or by exact name:

  • kidForAlgorithm(alg) returns a pre-provisioned key id matching the requested algorithm — used by the single-key operation flows.
  • requireKid(name) asserts that an exact, named cross-kid endpoint is present in the ankasecure.demo.kids catalogue — used by the cross-kid REENCRYPT / RESIGN flows that must operate on a specific granted source/target pair.

If cli.properties is missing the ankasecure.demo.kids line, the examples fail fast with a clear message instructing you to (re-)provision the demo-cli playground — they will not silently fall back.

PKCS#7 / CMS examples (Flow 19 & Flow 20)

The PKCS#7 interop examples operate on a packaged, non-sensitive CMS EnvelopedData fixture — you do not supply a PKCS#7 file:

  • Fixture: src/main/resources/pkcs7/enveloped-data-sample.p7m, loaded from the classpath by Pkcs7ExampleFixture. It is a single-recipient, zero-signer EnvelopedData artifact from the QA test signer; it carries no private key and no secret.
  • Flow 19 (analysis) needs no key. Structural analysis (analyzePkcs7 / analyzePkcs7Stream) inspects the CMS envelope metadata only, so it runs with no decryption key and no extra configuration.
  • Flow 20 (conversion) needs a pre-provisioned recipient key. Converting EnvelopedData to JWE requires the platform to decrypt the envelope, so it needs the recipient's private key referenced by the pkcs7.decryptionKid property in cli.properties. That key is matched by issuer DN + serial number and is imported by a control-plane step (outside the data-plane SDK); until it is provisioned, Flow 20's conversion calls fail at the server while Flow 19 still runs.
  • Data-plane only. Like every flow example, these never import keystores or perform any key-lifecycle operation.

When to use it

  • Archive upgrades -- you already store long-term backups or database dumps under EC/RSA and need to flip them to PQC strength in one pass.

  • Compliance deadlines -- regulators or customers demand post-quantum readiness but re-encrypting locally would blow storage or bandwidth budgets.

  • Cloud/offline hand-offs -- hand encrypted artefacts to a new environment (e.g., cold-storage, multi-cloud) while hardening the key type in a single API call.

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/ExampleScenario8.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.AuthenticatedSdk;
import co.ankatech.ankasecure.sdk.model.DecryptResultMetadata;
import co.ankatech.ankasecure.sdk.model.EncryptResult;
import co.ankatech.ankasecure.sdk.model.ReencryptResult;
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 8 — ML-KEM-768 → ML-KEM-1024 Bulk Re-encryption (auto-detect / Compact JWE).
 *
 * <p>This scenario performs a <strong>non-streaming</strong> PQC-to-PQC migration
 * of ciphertext from an ML-KEM-768 key to a stronger ML-KEM-1024 key, entirely on
 * the server side (no plaintext exposure):</p>
 *
 * <p>The SDK is data-plane-only: it does not create keys. This scenario operates
 * on the <strong>pre-provisioned granted cross-kid pair</strong> of the
 * {@code demo-cli} playground &mdash; source {@code cli-reencrypt3-source}
 * ({@code ML-KEM-768}) and target {@code cli-reencrypt3-target} ({@code ML-KEM-1024}).
 * The {@code cli-reference} actor holds the cross-kid REENCRYPT grant on this pair
 * plus the atomic encrypt/decrypt grants on each endpoint.</p>
 *
 * <ol>
 *   <li>Resolve the granted {@code ML-KEM-768} source and {@code ML-KEM-1024} target kids.</li>
 *   <li>Encrypt a file under the source (auto-detected Compact&nbsp;JWE).</li>
 *   <li>Re-encrypt the ciphertext on the server (ML-KEM-768 &rarr; ML-KEM-1024).</li>
 *   <li>Decrypt the new KEM ciphertext under the target.</li>
 *   <li>Validate round-trip integrity.</li>
 * </ol>
 *
 * <p><b>Implementation notes (Java&nbsp;21+):</b></p>
 * <ul>
 *   <li>Filesystem operations rely on the {@link java.nio.file.Path} API.</li>
 *   <li>UTF-8 encoding is enforced explicitly for deterministic behaviour.</li>
 *   <li>Temporary artefacts are written under <kbd>temp_files/</kbd>.</li>
 * </ul>
 *
 * <p><b>Thread-safety:</b> this class is stateless and immutable.</p>
 *
 * @author ANKATech Solutions Inc.
 * @since 3.0.0
 * @see ExampleUtil
 * @see ExamplePlaygroundKeys
 * @see AuthenticatedSdk
 */
public final class ExampleScenario8 {

    /** No instantiation &mdash; this class only exposes {@link #main(String[])}. */
    private ExampleScenario8() { }

    /**
     * Runs the ML-KEM-768 to ML-KEM-1024 re-encryption scenario on the granted pair.
     *
     * <p>Loads CLI properties, authenticates against ANKASecure&copy;,
     * and delegates to the scenario logic. On any unrecoverable error
     * the JVM terminates via
     * {@link ExampleUtil#fatal(String, Throwable)}.</p>
     *
     * @param args command-line arguments (ignored)
     */
    public static void main(final String[] args) {

        System.out.println("===== SCENARIO 8 START =====");
        System.out.println("""
                Purpose :
                  * Re-encrypt from ML-KEM-768 to ML-KEM-1024 (non-streaming helpers)
                  * Uses the pre-provisioned granted cross-kid pair
                    (cli-reencrypt3-source / cli-reencrypt3-target)
                  * Demonstrates encryptFile -> reencryptFile -> decryptFile APIs
                Steps   :
                  1) Resolve granted ML-KEM-768 source + ML-KEM-1024 target kids
                  2) Encrypt payload under source (auto-detected compact JWE)
                  3) Re-encrypt ciphertext to target
                  4) Decrypt KEM ciphertext under target
                  5) Validate integrity
                --------------------------------------------------------------""");

        try {
            prepareWorkingDir();

            Properties            props      = loadProperties();
            AuthenticatedSdk      sdk        = authenticate(props);
            ExamplePlaygroundKeys playground = ExamplePlaygroundKeys.from(props, sdk);

            runScenario(sdk, playground);

        } catch (Exception ex) {
            fatal("Scenario 8 failed", ex);
        }

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

    /**
     * Executes the ML-KEM-768 to ML-KEM-1024 re-encryption scenario on the granted pair.
     *
     * <ol>
     *   <li>Create plaintext file.</li>
     *   <li>Resolve the granted ML-KEM-768 source and ML-KEM-1024 target kids.</li>
     *   <li>Encrypt under the source (auto-detected compact JWE).</li>
     *   <li>Re-encrypt the ciphertext to the target (server-side).</li>
     *   <li>Decrypt KEM ciphertext under the target.</li>
     *   <li>Validate round-trip integrity.</li>
     * </ol>
     *
     * @param sdk        an authenticated {@link AuthenticatedSdk} instance;
     *                   must not be {@code null}
     * @param playground the pre-provisioned playground key resolver; must not be {@code null}
     * @throws Exception if any step fails
     */
    private static void runScenario(final AuthenticatedSdk sdk,
                                    final ExamplePlaygroundKeys playground) throws Exception {

        /* 1 -- create plaintext --------------------------------------------- */
        Path plain = TEMP_DIR.resolve("scenario8_plain.txt");
        FileIO.writeUtf8(plain,
                "Scenario-8 - ML-KEM-768 -> ML-KEM-1024 bulk re-encrypt demo.");
        System.out.println("[1] Plaintext ready          -> " + plain.toAbsolutePath());

        /* 2 -- resolve granted cross-kid pair (data-plane SDK: no key creation) */
        final String srcKid = playground.requireKid("cli-reencrypt3-source"); // ML-KEM-768
        final String kemKid = playground.requireKid("cli-reencrypt3-target"); // ML-KEM-1024
        System.out.println("[2] Source kid (ML-KEM-768)  -> " + srcKid);
        System.out.println("[2] Target kid (ML-KEM-1024) -> " + kemKid);

        Path srcJwe = TEMP_DIR.resolve("scenario8_src.jwe");
        EncryptResult srcMeta = sdk.encryptFile(srcKid, plain, srcJwe);
        System.out.println("[2] Ciphertext (ML-KEM-768)  -> " + srcJwe.toAbsolutePath());
        printEncryptMeta(srcMeta);

        /* 3 -- server-side re-encryption ML-KEM-768 -> ML-KEM-1024 ---------- */
        Path kemJwe = TEMP_DIR.resolve("scenario8_kem.jwe");
        ReencryptResult reMeta = sdk.reencryptFile(kemKid, srcJwe, kemJwe);
        System.out.println("[3] Ciphertext (ML-KEM-1024) -> " + kemJwe.toAbsolutePath());
        printReencryptMeta(reMeta);

        /* 4 -- decrypt KEM ciphertext --------------------------------------- */
        Path recovered = TEMP_DIR.resolve("scenario8_dec.txt");
        DecryptResultMetadata decMeta = sdk.decryptFile(kemJwe, recovered);
        System.out.println("[4] Decrypted file           -> " + recovered.toAbsolutePath());
        printDecryptMeta(decMeta);

        /* 5 -- integrity check ---------------------------------------------- */
        boolean match = FileIO.readUtf8(plain)
                .equals(FileIO.readUtf8(recovered));
        System.out.println(match
                ? "[5] SUCCESS - plaintext matches."
                : "[5] FAILURE - plaintext mismatch.");
    }
}

How to run


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

Console milestones

  • ML-KEM-768 source-kid resolution & compact-JWE encryption

  • ML-KEM-1024 target-kid resolution

  • Non-streaming server-side re-encryption (token upgraded)

  • Decryption with KEM key → SUCCESS (byte-perfect match)


Where next?