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:
-
Generate a post-quantum key ---
kty = ML-KEM
,alg = ML-KEM-1024
. -
Export its full JSON metadata (includes
publicKey
Base-64). -
Encrypt a plaintext file with
encryptFileUtilityStream
, feeding only the public key (detached JWE on disk). -
Decrypt that ciphertext in streaming mode via
decryptFileStream(kid, ...)
. -
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 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
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.