Skip to content

Flow 16 --- Utility-Stream Public-Key Encrypt (ML-KEM-1024) & Stream Decrypt

This scenario shows how to encrypt data client-side with an exported public key and decrypt it later on the server with the corresponding ML-KEM-1024 key:

  1. Generate a post-quantum key --- kty = ML-KEM, alg = ML-KEM-1024.

  2. Export its full JSON metadata (includes publicKey Base-64).

  3. Encrypt a plaintext file with encryptFileUtilityStream, feeding only the public key (detached JWE on disk).

  4. Decrypt that ciphertext in streaming mode via decryptFileStream(kid, ...).

  5. Validate that the recovered bytes equal the original.

Highlights

  • Pure public-key encryption on the client, no secret material required.
  • Handles any file size thanks to streaming helpers.
  • End-to-end integrity confirmed by comparing plaintexts.

When to use it

  • Browser uploads --- encrypt files locally before sending them to the server.

  • Partner hand-off --- share only the JSON export so third -parties can encrypt for you.

  • Backup pipelines --- detach encryption from the HSM to reduce round-trips.

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/ExampleScenario16.java

/* *****************************************************************************
 * FILE: ExampleScenario16.java
 * Copyright © 2025 Anka Technologies.
 * SPDX-License-Identifier: MIT
 * ---------------------------------------------------------------------------
 * Scenario 16 – Utility-Stream Public-Key Encryption (ML-KEM-1024)
 * ---------------------------------------------------------------------------
 * Demonstrates how to:
 *   * Generate an ML-KEM-1024 key.
 *   * Export its public metadata (Base64 <code>publicKey</code>) to JSON.
 *   * Encrypt a file using <em>utility-stream</em> (detached JWE) with only
 *     the public key.
 *
 * For non-streaming (compact JWE) encryption, use
 * {@link co.ankatech.ankasecure.sdk.AnkaSecureSdk#encryptFile}.
 *
 * All transient artefacts are written under <kbd>temp_files/</kbd>.
 * ****************************************************************************/
package co.ankatech.ankasecure.sdk.examples;

import co.ankatech.ankasecure.sdk.AnkaSecureSdk;
import co.ankatech.ankasecure.sdk.model.EncryptResult;
import co.ankatech.ankasecure.sdk.model.GenerateKeySpec;
import co.ankatech.ankasecure.sdk.model.ImportKeySpec;
import com.google.gson.*;
import java.io.IOException;
import java.io.Reader;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Properties;

/**
 * <h2>Scenario&nbsp;16 – Utility-Stream Encryption (ML-KEM-1024)</h2>
 *
 * <p>This scenario shows how to encrypt data with a <strong>public-only</strong>
 * ML-KEM-1024 key via the <em>utility-stream</em> API, producing a detached
 * JWE. The private key never touches the client.</p>
 *
 * <ol>
 *   <li>Generate an ML-KEM-1024 key.</li>
 *   <li>Export its metadata (extract the Base64-encoded public key).</li>
 *   <li>Encrypt a file using only that public key.</li>
 * </ol>
 *
 * @author ANKATech – Security Engineering
 */
public final class ExampleScenario16 {

    /** Working directory for all artefacts. */
    private static final Path TEMP_DIR = Path.of("temp_files");

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

        System.out.println("===== SCENARIO 16 START =====");
        System.out.println("""
                Purpose :
                  * Generate an ML-KEM-1024 key.
                  * Export its Base64 publicKey.
                  * Encrypt a file in streaming (detached JWE) mode with only that key.
                Steps   :
                  1) Generate PQC key
                  2) Export key metadata
                  3) Utility-stream encrypt
                --------------------------------------------------------------""");

        try {
            ensureTempDir(TEMP_DIR);

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

            runScenario(sdk);

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

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

    /* ====================================================================== */
    /** Executes all steps of the scenario. */
    private static void runScenario(final AnkaSecureSdk sdk) throws Exception {

        /* 1 ── generate ML-KEM-1024 key -------------------------------- */
        String kid = "sc16_kem1024_" + System.currentTimeMillis();
        sdk.generateKey(new GenerateKeySpec()
                .setKid(kid)
                .setKty("ML-KEM")
                .setAlg("ML-KEM-1024"));
        System.out.println("[1] Key generated            -> kid = " + kid);

        /* 2 ── export metadata ---------------------------------------- */
        Path metaJson = TEMP_DIR.resolve("scenario16_key.json");
        sdk.exportKey(kid, metaJson);
        System.out.println("[2] Metadata exported        -> " + metaJson.toAbsolutePath());

        /* 3 ── prepare plaintext -------------------------------------- */
        Path data = TEMP_DIR.resolve("scenario16_plain.txt");
        Files.writeString(
                data,
                "Scenario 16 – ML-KEM-1024 utility-stream demo.",
                StandardCharsets.UTF_8);
        System.out.println("[3] Data file prepared       -> " + data.toAbsolutePath());

        /* 4 ── utility-stream encrypt --------------------------------- */
        ImportKeySpec meta = readMetadata(metaJson);
        String pubKeyB64   = meta.getPublicKey();
        if (pubKeyB64 == null || pubKeyB64.isBlank()) {
            fatal("Exported JSON missing publicKey", null);
        }

        Path cipher = TEMP_DIR.resolve("scenario16_detached.jwe");
        EncryptResult enc = sdk.encryptFileUtilityStream(
                meta.getKty(),
                meta.getAlg(),
                pubKeyB64,
                data,
                cipher);
        System.out.println("[4] Encrypted output         -> " + cipher.toAbsolutePath());
        printEncryptMeta(enc);
    }

    /* ====================================================================== */
    /** Logs encryption-side metadata. */
    private static void printEncryptMeta(final EncryptResult r) {
        System.out.println("    * Key requested : " + nullSafe(r.getKeyRequested()));
        System.out.println("    * Key used      : " + nullSafe(r.getActualKeyUsed()));
        System.out.println("    * Algorithm     : " + nullSafe(r.getAlgorithmUsed()));
        printWarnings(r.getWarnings());
    }

    /** Prints warnings, if any. */
    private static void printWarnings(final List<String> warnings) {
        if (warnings != null && !warnings.isEmpty()) {
            System.out.println("    * Warnings:");
            warnings.forEach(w -> System.out.println("      * " + w));
        }
    }

    /* ====================================================================== */
    /** Parses the exported key JSON into an {@link ImportKeySpec}. */
    private static ImportKeySpec readMetadata(final Path json) throws IOException {
        try (Reader r = Files.newBufferedReader(json, StandardCharsets.UTF_8)) {
            Gson gson = new GsonBuilder()
                    .disableHtmlEscaping()
                    .registerTypeAdapter(
                            ZonedDateTime.class,
                            new JsonDeserializer<ZonedDateTime>() {
                                @Override
                                public ZonedDateTime deserialize(
                                        JsonElement el, Type type,
                                        JsonDeserializationContext ctx) {
                                    return ZonedDateTime.parse(
                                            el.getAsString(),
                                            DateTimeFormatter.ISO_OFFSET_DATE_TIME);
                                }
                            })
                    .registerTypeAdapter(
                            ZonedDateTime.class,
                            (JsonSerializer<ZonedDateTime>) (src, type, ctx) ->
                                    new JsonPrimitive(
                                            src.format(
                                                    DateTimeFormatter.ISO_OFFSET_DATE_TIME)))
                    .setPrettyPrinting()
                    .create();
            return gson.fromJson(r, ImportKeySpec.class);
        }
    }

    /* ====================================================================== */
    /** Returns a printable value or <code>(none)</code> when null/blank. */
    private static String nullSafe(final String s) {
        return (s == null || s.isBlank()) ? "(none)" : s;
    }

    /** Ensures <code>dir</code> exists. */
    private static void ensureTempDir(final Path dir) throws IOException {
        if (!Files.exists(dir)) {
            Files.createDirectories(dir);
        }
    }

    /** Logs an unrecoverable error and terminates. */
    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 constructor prevents instantiation.
     */
    private ExampleScenario16() {
        /* utility class – no instantiation */
    }
}

How to run

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

Console milestones

  • ML-KEM-1024 key generation (kid = sc16_kem1024_*)

  • Key-metadata export → sc16_key.json

  • Utility-stream encryption → sc16_detached.jwe

  • SUCCESS message confirming encryption (decryption demonstrated in companion flow 17)


Where next?

© 2025 Anka Technologies. All rights reserved.