Skip to content

Flow 1 – ML-KEM-512 Stream Encrypt / Decrypt (Detached JWET)

This scenario shows a complete post-quantum encryption round-trip with the streaming endpoints:

  1. Resolve the pre-provisioned PQC key-pair (ML-KEM-512).
  2. Stream-encrypt a plaintext file → detached JWET (General-JSON header + binary part).
  3. Stream-decrypt the ciphertext.
  4. Validate that the decrypted data matches the original.

Key points

  • Leverages the streaming helpers encryptFileStream / decryptFileStream, letting you encrypt or decrypt multi-gigabyte files while holding only a few kB in memory.\
  • Emits a detached JWET (General JSON): header + tag stay in a tiny JSON structure, the raw ciphertext streams as-is — zero Base64 expansion, ideal for direct‐to-disk or direct-to-object-store writes.\
  • Illustrates a full post-quantum ML-KEM-512 data-plane round-trip (resolve key ➜ encrypt ➜ decrypt) and surfaces rich key-usage telemetry in HTTP headers so you can monitor rotations, expiry or soft-limit warnings in real time.

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

  • Massive datasets that must stay encrypted at rest — think nightly database dumps, genomic archives, video libraries or research data running into tens or hundreds of GB. Streaming keeps RAM flat while detached JWET avoids Base64 bloat.
  • Write-once / read-seldom storage tiers — because the ciphertext is pure binary, you can pipe it straight to S3, GCS, Azure Blob or tape without post-processing; the slim JSON header is all you need to catalogue or audit the object later.
  • Long-term confidentiality with quantum resilience — ML-KEM-512 protects backups and legal holds that must remain secret beyond the expected RSA/ECC break horizon, giving confidence for 10- to 20-year data-retention mandates.

Dependency — this example imports co.ankatech.ankasecure.sdk.examples.ExampleUtil.
If you have not copied that class yet, see example-util.md.


Complete Java implementation

src/main/java/co/ankatech/ankasecure/sdk/examples/ExampleScenario1.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.util.FileIO;
import java.nio.file.Path;
import java.util.Properties;

import static co.ankatech.ankasecure.sdk.examples.ExampleUtil.*;

/**
 * Scenario 1 — End-to-End ML-KEM-512 Encryption (Streaming).
 *
 * <p>This scenario walks through a complete post-quantum data-plane round-trip
 * using the <strong>streaming</strong> endpoints of ANKASecure&copy;. In streaming
 * mode the service produces a <em>detached&nbsp;JWE</em> (General-JSON header + raw
 * ciphertext stream) instead of a single compact JWE string.</p>
 *
 * <ol>
 *   <li>Resolve a pre-provisioned <code>ML-KEM-512</code> key from the demo-cli
 *       playground (the SDK is data-plane-only — it does not create keys).</li>
 *   <li>Stream-encrypt a local file (detached JWE).</li>
 *   <li>Stream-decrypt the ciphertext.</li>
 *   <li>Validate round-trip integrity.</li>
 * </ol>
 *
 * <p><strong>Prerequisite:</strong> a {@code cli.properties} for the
 * {@code cli-reference} actor of the provisioned {@code demo-cli} playground,
 * carrying the {@code ankasecure.demo.kids} catalogue (emitted by the
 * demo-provisioning tool). Key lifecycle (generate/rotate/export) lives in
 * {@code cli-admin}, not the SDK.</p>
 *
 * <p><b>Implementation notes (Java&nbsp;21+):</b></p>
 * <ul>
 *   <li>All file handling relies on the {@link java.nio.file.Path} API.</li>
 *   <li>UTF-8 is enforced explicitly to avoid platform-dependent defaults.</li>
 *   <li>Temporary artefacts are written under <kbd>temp_files/</kbd>.</li>
 * </ul>
 *
 * <p><b>Thread-safety:</b> the class is stateless and immutable; a fresh
 * {@link AuthenticatedSdk} instance is created inside {@link #main(String[])}.</p>
 *
 * @author ANKATech Solutions Inc.
 * @since 3.0.0
 * @see ExampleUtil
 * @see ExamplePlaygroundKeys
 * @see AuthenticatedSdk
 */
public final class ExampleScenario1 {

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

    /**
     * Runs the ML-KEM-512 streaming encryption scenario on a pre-provisioned key.
     *
     * <p>Loads CLI properties, authenticates against ANKASecure&copy;, resolves a
     * pre-provisioned ML-KEM-512 playground key, 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(String[] args) {

        System.out.println("===== SCENARIO 1 START =====");
        System.out.println("""
                Purpose :
                  * End-to-end ML-KEM-512 data-plane round-trip in streaming mode
                  * Demonstrates detached-JWE pipeline (encrypt -> decrypt) on a
                    pre-provisioned playground key
                Steps   :
                  1) Resolve pre-provisioned ML-KEM-512 key
                  2) Encrypt payload (detached JWE)
                  3) Decrypt ciphertext
                  4) 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 1 failed", ex);
        }

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

    /**
     * Executes the ML-KEM-512 streaming encryption scenario on a pre-provisioned key.
     *
     * <ol>
     *   <li>Create plaintext file.</li>
     *   <li>Resolve a pre-provisioned ML-KEM-512 key.</li>
     *   <li>Stream-encrypt to detached JWE.</li>
     *   <li>Stream-decrypt and validate 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 on any I/O or cryptographic failure
     */
    private static void runScenario(final AuthenticatedSdk sdk,
                                    final ExamplePlaygroundKeys playground) throws Exception {

        /* -- file system artefacts ----------------------------------------- */
        Path plainFile = TEMP_DIR.resolve("scenario1_plain.txt");
        Path encFile   = TEMP_DIR.resolve("scenario1.enc");
        Path decFile   = TEMP_DIR.resolve("scenario1.dec");

        /* 1 -- plaintext ---------------------------------------------------- */
        FileIO.writeUtf8(plainFile,
                "Hello Scenario-1 - streaming encryption demo!");
        System.out.println("[1] Plaintext ready           -> " + plainFile.toAbsolutePath());

        /* 2 -- resolve pre-provisioned key (data-plane SDK: no key creation) */
        final String kid = playground.kidForAlgorithm("ML-KEM-512");
        System.out.println("[2] Using pre-provisioned key -> kid = " + kid);

        /* 3 -- streaming encrypt (detached JWE) ----------------------------- */
        EncryptResult encMeta = sdk.encryptFileStream(kid, plainFile, encFile);
        System.out.println("[3] Ciphertext written        -> " + encFile.toAbsolutePath());
        printEncryptMeta(encMeta);

        /* 4 -- streaming decrypt -------------------------------------------- */
        DecryptResultMetadata decMeta = sdk.decryptFileStream(encFile, decFile);
        System.out.println("[4] Decrypted file            -> " + decFile.toAbsolutePath());
        printDecryptMeta(decMeta);

        /* 5 -- validation --------------------------------------------------- */
        String original  = FileIO.readUtf8(plainFile);
        String recovered = FileIO.readUtf8(decFile);
        System.out.println(original.equals(recovered)
                ? "[5] Validation OK - plaintext matches."
                : "[5] WARNING - plaintext mismatch!");
    }
}

How to run


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

Console milestones:

  • ML-KEM-512 key resolution from the playground

  • Key metadata printed to the console

  • Detached-JWET stream-encryption → scenario1.enc

  • Stream-decryption → scenario1.dec

  • Validation OK -- byte-perfect round-trip


Where next?