Skip to content

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

This scenario walks through a non-streaming, server-side migration from a classical elliptic-curve key to a post-quantum KEM:

  • Generate an EC-521 key-pair (kty="EC").

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

  • Generate an ML-KEM-768 key (kty="ML-KEM").

  • Re-encrypt the ciphertext EC-521 ➜ ML-KEM-768 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.

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

/* *****************************************************************************
 * FILE: ExampleScenario8.java
 * Copyright © 2025 Anka Technologies.
 * ---------------------------------------------------------------------------
 * Scenario 8 – EC-521 → ML-KEM-768 Bulk Re-encryption (Compact JWE helpers)
 * ---------------------------------------------------------------------------
 *  1. Generate an EC-521 key and encrypt a file (Compact JWE).
 *  2. Generate an ML-KEM-768 key.
 *  3. Re-encrypt the ciphertext server-side (EC-521 → ML-KEM-768) – no
 *     plaintext exposure on the client.
 *  4. Decrypt the fresh ML-KEM JWE.
 *  5. Validate round-trip integrity and print rich metadata.
 *
 * All transient artefacts are created in <temp_files/>.
 * ****************************************************************************/
package co.ankatech.ankasecure.sdk.examples;

import co.ankatech.ankasecure.sdk.AnkaSecureSdk;
import co.ankatech.ankasecure.sdk.model.DecryptResultMetadata;
import co.ankatech.ankasecure.sdk.model.EncryptResult;
import co.ankatech.ankasecure.sdk.model.GenerateKeySpec;
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.*;

/**
 * <h2>Scenario&nbsp;8 – EC-521 → ML-KEM-768 Bulk Re-encryption (Compact&nbsp;JWE)</h2>
 *
 * <p>This scenario demonstrates a <strong>non-streaming</strong> migration of
 * ciphertext from a classical elliptic-curve key (EC-521) to a post-quantum
 * ML-KEM-768 key, entirely on the server side and therefore without revealing
 * plaintext to the client:</p>
 *
 * <ol>
 *   <li>Generate an EC-521 key and encrypt a file (Compact&nbsp;JWE).</li>
 *   <li>Generate an ML-KEM-768 key.</li>
 *   <li>Re-encrypt the EC ciphertext on the server (EC-521 → ML-KEM-768).</li>
 *   <li>Decrypt the new KEM ciphertext.</li>
 *   <li>Validate round-trip integrity.</li>
 * </ol>
 *
 * <p><b>Implementation notes (Java&nbsp;21+):</b></p>
 * <ul>
 *   <li>All filesystem operations use the {@link java.nio.file.Path} API.</li>
 *   <li>UTF-8 encoding is specified explicitly for deterministic behaviour.</li>
 *   <li>Directory creation delegates to {@link ExampleUtil#ensureTempDir(Path)}.</li>
 * </ul>
 *
 * <p><b>Thread-safety:</b> this class is stateless and immutable.</p>
 *
 * @author ANKATech – Security Engineering
 * @since 2.1.0
 */
public final class ExampleScenario8 {

    /* ====================================================================== */
    /**
     * Entry-point.
     *
     * @param args ignored
     */
    public static void main(final String[] args) {

        System.out.println("===== SCENARIO 8 START =====");
        System.out.println("""
                Purpose :
                  * Re-encrypt Compact-JWE from EC-521 to ML-KEM-768 (non-streaming helpers)
                  * Demonstrates encryptFile → reencryptFile → decryptFile
                --------------------------------------------------------------""");

        try {
            ensureTempDir(TEMP_DIR);

            Properties props = loadProperties();
            AnkaSecureSdk sdk = authenticate(props);

            runScenario(sdk);

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

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

    /* ====================================================================== */
    /**
     * Executes all steps of Scenario&nbsp;8.
     *
     * @param sdk an authenticated {@link AnkaSecureSdk} instance
     *
     * @throws Exception if any step fails
     */
    private static void runScenario(final AnkaSecureSdk sdk) throws Exception {

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

        /* 2 ── EC-521 key + encrypt ----------------------------------- */
        String ecKid = "sc8_ec521_" + System.currentTimeMillis();
        sdk.generateKey(new GenerateKeySpec()
                .setKid(ecKid)
                .setKty("EC")
                .setAlg("EC-521"));
        System.out.println("[2] EC-521 key generated   -> kid = " + ecKid);

        Path ecJwe = TEMP_DIR.resolve("scenario8_ec.jwe");
        EncryptResult ecMeta = sdk.encryptFile(ecKid, plain, ecJwe);
        System.out.println("[2] Ciphertext (EC-521)    -> " + ecJwe.toAbsolutePath());
        printEncryptMeta(ecMeta);

        /* 3 ── ML-KEM-768 key ---------------------------------------- */
        String kemKid = "sc8_kem768_" + System.currentTimeMillis();
        sdk.generateKey(new GenerateKeySpec()
                .setKid(kemKid)
                .setKty("ML-KEM")
                .setAlg("ML-KEM-768"));
        System.out.println("[3] ML-KEM-768 key generated-> kid = " + kemKid);

        /* 4 ── server re-encryption EC → KEM -------------------------- */
        Path kemJwe = TEMP_DIR.resolve("scenario8_kem.jwe");
        ReencryptResult reMeta = sdk.reencryptFile(kemKid, ecJwe, kemJwe);
        System.out.println("[4] Ciphertext (ML-KEM)    -> " + kemJwe.toAbsolutePath());
        printReencryptMeta(reMeta);

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

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

    /**
     * Private constructor prevents instantiation.
     */
    private ExampleScenario8() {
        /* utility class – no instantiation */
    }
}

How to run


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

Console milestones

  • EC-521 key generation & compact-JWE encryption

  • ML-KEM-768 key generation

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

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


Where next?