Flow 8 -- P-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 a
P-521key-pair (kty="EC",alg="P-521"). -
Encrypt a plaintext file → compact JWE (five B64URL segments).
-
Generate an
ML-KEM-768key (kty="ML-KEM"). -
Re-encrypt the ciphertext P-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 → decryptFilehelpers -- 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
/*
* 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.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.*;
/**
* Scenario 8 — P-521 → ML-KEM-768 Bulk Re-encryption (Compact JWE).
*
* <p>This scenario performs a <strong>non-streaming</strong> migration of
* ciphertext from a classical P-521 key to a post-quantum ML-KEM-768 key,
* entirely on the server side (no plaintext exposure):</p>
*
* <ol>
* <li>Generate a P-521 key and encrypt a file (Compact JWE).</li>
* <li>Generate an ML-KEM-768 key.</li>
* <li>Re-encrypt the EC ciphertext on the server (P-521 → ML-KEM-768).</li>
* <li>Decrypt the new KEM ciphertext.</li>
* <li>Validate round-trip integrity.</li>
* </ol>
*
* <p><b>Implementation notes (Java 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 AuthenticatedSdk
*/
public final class ExampleScenario8 {
/** No instantiation — this class only exposes {@link #main(String[])}. */
private ExampleScenario8() { }
/**
* Runs the P-521 to ML-KEM-768 compact JWE re-encryption scenario.
*
* <p>Loads CLI properties, authenticates against ANKASecure©,
* 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 Compact-JWE from P-521 to ML-KEM-768 (non-streaming helpers)
* Demonstrates encryptFile -> reencryptFile -> decryptFile APIs
Steps :
1) Generate P-521 key
2) Encrypt payload (compact JWE)
3) Generate ML-KEM-768 key
4) Re-encrypt ciphertext
5) Decrypt KEM ciphertext
6) Validate integrity
--------------------------------------------------------------""");
try {
ensureTempDir(TEMP_DIR);
Properties props = loadProperties();
AuthenticatedSdk sdk = authenticate(props);
runScenario(sdk);
} catch (Exception ex) {
fatal("Scenario 8 failed", ex);
}
System.out.println("===== SCENARIO 8 END =====");
}
/**
* Executes the 6-step P-521 to ML-KEM-768 re-encryption scenario.
*
* <ol>
* <li>Create plaintext file.</li>
* <li>Generate P-521 key and encrypt.</li>
* <li>Generate ML-KEM-768 key.</li>
* <li>Re-encrypt EC ciphertext to KEM (server-side).</li>
* <li>Decrypt KEM ciphertext.</li>
* <li>Validate round-trip integrity.</li>
* </ol>
*
* @param sdk an authenticated {@link AuthenticatedSdk} instance;
* must not be {@code null}
* @throws Exception if any step fails
*/
private static void runScenario(final AuthenticatedSdk sdk) throws Exception {
/* 1 -- create plaintext --------------------------------------------- */
Path plain = TEMP_DIR.resolve("scenario8_plain.txt");
FileIO.writeUtf8(plain,
"Scenario-8 - P-521 -> ML-KEM-768 bulk re-encrypt demo.");
System.out.println("[1] Plaintext ready -> " + plain.toAbsolutePath());
/* 2 -- P-521 key + encrypt ------------------------------------------ */
String ecKid = "sc8_p521_" + System.currentTimeMillis();
sdk.generateKey(new GenerateKeySpec()
.setKid(ecKid)
.setKty("EC")
.setAlg("P-521"));
System.out.println("[2] P-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 (P-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-side 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.");
}
}
How to run
Console milestones
-
P-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)