ML-KEM-512 Encrypt / Decrypt (compact, PQC Level 1)

ML-KEM-512 — the compact NIST Level 1 post-quantum KEM. Same flow as ML-KEM-1024, smaller ciphertexts, useful for high-throughput scenarios.

🔐 ML-KEM-512 Encrypt / Decrypt (compact)

Scenario: provision an ML-KEM-512 key (NIST FIPS 203 post-quantum KEM, security level 1) and use it for encrypt / decrypt. Choose this over ML-KEM-1024 when you need the smallest ciphertext / key sizes and NIST Level 1 (~AES-128 equivalent against classical attacks) is acceptable.

Prerequisites

Same two identities as the AES-256 recipe: admin (password grant → ankasecure-admin) + application (client_credentials → ankasecure-core).


Step-by-step

export BASE_URL="https://staging.ankatech.co"
export ADMIN_EMAIL="[email protected]" ADMIN_PASSWORD="••••••••••••"
export TENANT_ID="00000000-0000-0000-0000-000000000003"
export CLIENT_ID="00000000-0000-0000-0000-000000000000"
export CLIENT_SECRET="••••••••••••••••••••••••••••••••"
export KID="my-mlkem512-key"

# Step 1a: admin token
export ADMIN_TOKEN=$(curl -s -X POST "$BASE_URL/api/v3/auth/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=password" --data-urlencode "username=$ADMIN_EMAIL" \
  --data-urlencode "password=$ADMIN_PASSWORD" --data-urlencode "tenant_id=$TENANT_ID" \
  | jq -r .access_token)

# Step 1b: app token
export APP_TOKEN=$(curl -s -X POST "$BASE_URL/api/v3/auth/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=client_credentials" \
  --data-urlencode "client_id=$CLIENT_ID" --data-urlencode "client_secret=$CLIENT_SECRET" \
  | jq -r .access_token)

# Step 2: create ML-KEM-512 key (admin)
curl -s -X POST "$BASE_URL/api/v3/admin/tenants/$TENANT_ID/keys" \
  -H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" \
  -d "{
    \"kid\":\"$KID\", \"kty\":\"ML-KEM\", \"algorithm\":\"ML-KEM-512\",
    \"purpose\":\"ENCRYPT_DECRYPT\", \"keyOps\":[\"encrypt\",\"decrypt\"],
    \"exportable\": true
  }" | jq

# Step 3: encrypt (app)
export DATA_B64=$(printf "%s" "Compact PQC payload" | base64 -w0)
curl -s -X POST "$BASE_URL/api/v3/crypto/encrypt" \
  -H "Authorization: Bearer $APP_TOKEN" -H "Content-Type: application/json" \
  -d "{ \"kid\":\"$KID\", \"data\":\"$DATA_B64\" }" | tee /tmp/enc.json | jq '.algorithmUsed'
# → "ML-KEM-512+A256GCM"

# Step 4: decrypt (app)
curl -s -X POST "$BASE_URL/api/v3/crypto/decrypt" \
  -H "Authorization: Bearer $APP_TOKEN" -H "Content-Type: application/json" \
  -d "{ \"jweToken\": $(jq -c .jweToken /tmp/enc.json) }" \
  | jq -r .decryptedData | base64 -d
import base64, requests

BASE = "https://staging.ankatech.co"
TENANT_ID = "00000000-0000-0000-0000-000000000003"
KID = "my-mlkem512-key"

def tok(fields): return requests.post(f"{BASE}/api/v3/auth/token", data=fields).json()["access_token"]

admin = {"Authorization": f"Bearer {tok({'grant_type':'password','username':'[email protected]','password':'•••','tenant_id':TENANT_ID})}"}
app   = {"Authorization": f"Bearer {tok({'grant_type':'client_credentials','client_id':'…','client_secret':'…'})}"}

# Create ML-KEM-512 key (admin)
requests.post(f"{BASE}/api/v3/admin/tenants/{TENANT_ID}/keys", headers=admin, json={
    "kid": KID, "kty": "ML-KEM", "algorithm": "ML-KEM-512",
    "purpose": "ENCRYPT_DECRYPT", "keyOps": ["encrypt","decrypt"], "exportable": True,
}).raise_for_status()

# Encrypt + Decrypt (app)
data_b64 = base64.b64encode(b"Compact PQC payload").decode()
enc = requests.post(f"{BASE}/api/v3/crypto/encrypt", headers=app,
                    json={"kid": KID, "data": data_b64}).json()
print("algorithmUsed:", enc["algorithmUsed"])

dec = requests.post(f"{BASE}/api/v3/crypto/decrypt", headers=app,
                    json={"jweToken": enc["jweToken"]}).json()
print("plaintext:", base64.b64decode(dec["decryptedData"]).decode())
import java.net.URI;
import java.net.http.*;
import java.util.Base64;
import com.fasterxml.jackson.databind.*;

public class MlKem512RoundTrip {

    static final String BASE_URL       = "https://staging.ankatech.co";
    static final String ADMIN_EMAIL    = "[email protected]";
    static final String ADMIN_PASSWORD = "••••••••••••";
    static final String TENANT_ID      = "00000000-0000-0000-0000-000000000003";
    static final String CLIENT_ID      = "00000000-0000-0000-0000-000000000000";
    static final String CLIENT_SECRET  = "••••••••••••••••••••••••••••••••";
    static final String KID            = "my-mlkem512-key";

    static final HttpClient   http = HttpClient.newHttpClient();
    static final ObjectMapper json = new ObjectMapper();

    static String token(String body) throws Exception {
        var r = http.send(HttpRequest.newBuilder(URI.create(BASE_URL + "/api/v3/auth/token"))
            .header("Content-Type", "application/x-www-form-urlencoded")
            .POST(HttpRequest.BodyPublishers.ofString(body)).build(),
            HttpResponse.BodyHandlers.ofString());
        return json.readTree(r.body()).get("access_token").asText();
    }

    public static void main(String[] args) throws Exception {
        var adminAuth = "Bearer " + token("grant_type=password&username=" + ADMIN_EMAIL
            + "&password=" + ADMIN_PASSWORD + "&tenant_id=" + TENANT_ID);
        var appAuth   = "Bearer " + token("grant_type=client_credentials&client_id="
            + CLIENT_ID + "&client_secret=" + CLIENT_SECRET);

        // Create ML-KEM-512 key (admin) — only kty/algorithm differ vs AES-256
        http.send(HttpRequest.newBuilder(URI.create(
            BASE_URL + "/api/v3/admin/tenants/" + TENANT_ID + "/keys"))
            .header("Authorization", adminAuth).header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString("""
                { "kid": "%s", "kty": "ML-KEM", "algorithm": "ML-KEM-512",
                  "purpose": "ENCRYPT_DECRYPT", "keyOps": ["encrypt","decrypt"],
                  "exportable": true }""".formatted(KID))).build(),
            HttpResponse.BodyHandlers.ofString());

        // Encrypt + decrypt (app)
        var dataB64 = Base64.getEncoder().encodeToString("Compact PQC payload".getBytes());
        var encResp = http.send(HttpRequest.newBuilder(URI.create(BASE_URL + "/api/v3/crypto/encrypt"))
            .header("Authorization", appAuth).header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(
                "{ \"kid\": \"" + KID + "\", \"data\": \"" + dataB64 + "\" }")).build(),
            HttpResponse.BodyHandlers.ofString());
        var enc = json.readTree(encResp.body());
        System.out.println("algorithmUsed=" + enc.get("algorithmUsed").asText());

        var decResp = http.send(HttpRequest.newBuilder(URI.create(BASE_URL + "/api/v3/crypto/decrypt"))
            .header("Authorization", appAuth).header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(
                "{ \"jweToken\": " + enc.get("jweToken") + " }")).build(),
            HttpResponse.BodyHandlers.ofString());
        var plaintext = new String(Base64.getDecoder().decode(
            json.readTree(decResp.body()).get("decryptedData").asText()));
        System.out.println("plaintext=" + plaintext);
    }
}
const BASE_URL = "https://staging.ankatech.co";
const TENANT_ID = "00000000-0000-0000-0000-000000000003";
const KID = "my-mlkem512-key";

const tok = async (f) => (await (await fetch(`${BASE_URL}/api/v3/auth/token`, {
  method: "POST", headers: {"Content-Type": "application/x-www-form-urlencoded"},
  body: new URLSearchParams(f)})).json()).access_token;

const admin = { "Authorization": `Bearer ${await tok({grant_type:"password", username:"[email protected]", password:"•••", tenant_id:TENANT_ID})}` };
const app   = { "Authorization": `Bearer ${await tok({grant_type:"client_credentials", client_id:"…", client_secret:"…"})}` };
const jsonH = (a) => ({ ...a, "Content-Type": "application/json" });

// Create ML-KEM-512 key
await fetch(`${BASE_URL}/api/v3/admin/tenants/${TENANT_ID}/keys`, {
  method: "POST", headers: jsonH(admin),
  body: JSON.stringify({
    kid: KID, kty: "ML-KEM", algorithm: "ML-KEM-512",
    purpose: "ENCRYPT_DECRYPT", keyOps: ["encrypt","decrypt"], exportable: true,
  }),
});

// Encrypt + Decrypt
const dataB64 = Buffer.from("Compact PQC payload").toString("base64");
const enc = await (await fetch(`${BASE_URL}/api/v3/crypto/encrypt`, {
  method: "POST", headers: jsonH(app),
  body: JSON.stringify({ kid: KID, data: dataB64 }),
})).json();
console.log("algorithmUsed:", enc.algorithmUsed);

const dec = await (await fetch(`${BASE_URL}/api/v3/crypto/decrypt`, {
  method: "POST", headers: jsonH(app),
  body: JSON.stringify({ jweToken: enc.jweToken }),
})).json();
console.log("plaintext:", Buffer.from(dec.decryptedData, "base64").toString());
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

class MlKem512RoundTrip
{
    const string BASE_URL       = "https://staging.ankatech.co";
    const string ADMIN_EMAIL    = "[email protected]";
    const string ADMIN_PASSWORD = "••••••••••••";
    const string TENANT_ID      = "00000000-0000-0000-0000-000000000003";
    const string CLIENT_ID      = "00000000-0000-0000-0000-000000000000";
    const string CLIENT_SECRET  = "••••••••••••••••••••••••••••••••";
    const string KID            = "my-mlkem512-key";

    static async Task<string> GetToken(HttpClient http, IEnumerable<KeyValuePair<string,string>> fields)
    {
        var r = await http.PostAsync($"{BASE_URL}/api/v3/auth/token",
            new FormUrlEncodedContent(fields));
        return JsonDocument.Parse(await r.Content.ReadAsStringAsync())
                           .RootElement.GetProperty("access_token").GetString();
    }

    static async Task Main()
    {
        var http = new HttpClient();

        var adminToken = await GetToken(http, new[] {
            new KeyValuePair<string,string>("grant_type", "password"),
            new KeyValuePair<string,string>("username",   ADMIN_EMAIL),
            new KeyValuePair<string,string>("password",   ADMIN_PASSWORD),
            new KeyValuePair<string,string>("tenant_id",  TENANT_ID),
        });
        var appToken = await GetToken(http, new[] {
            new KeyValuePair<string,string>("grant_type",    "client_credentials"),
            new KeyValuePair<string,string>("client_id",     CLIENT_ID),
            new KeyValuePair<string,string>("client_secret", CLIENT_SECRET),
        });

        var adminHttp = new HttpClient();
        adminHttp.DefaultRequestHeaders.Add("Authorization", $"Bearer {adminToken}");
        var appHttp = new HttpClient();
        appHttp.DefaultRequestHeaders.Add("Authorization", $"Bearer {appToken}");

        // Create ML-KEM-512 key (admin)
        await adminHttp.PostAsync(
            $"{BASE_URL}/api/v3/admin/tenants/{TENANT_ID}/keys",
            new StringContent(JsonSerializer.Serialize(new {
                kid = KID, kty = "ML-KEM", algorithm = "ML-KEM-512",
                purpose = "ENCRYPT_DECRYPT", keyOps = new[] { "encrypt", "decrypt" },
                exportable = true,
            }), Encoding.UTF8, "application/json"));

        // Encrypt (app)
        var dataB64 = Convert.ToBase64String(Encoding.UTF8.GetBytes("Compact PQC payload"));
        var encResp = await appHttp.PostAsync(
            $"{BASE_URL}/api/v3/crypto/encrypt",
            new StringContent(JsonSerializer.Serialize(new { kid = KID, data = dataB64 }),
                              Encoding.UTF8, "application/json"));
        var enc = JsonDocument.Parse(await encResp.Content.ReadAsStringAsync()).RootElement;
        Console.WriteLine($"algorithmUsed={enc.GetProperty("algorithmUsed").GetString()}");

        // Decrypt (app)
        var jweToken = enc.GetProperty("jweToken").GetRawText();
        var decResp = await appHttp.PostAsync(
            $"{BASE_URL}/api/v3/crypto/decrypt",
            new StringContent($"{{ \"jweToken\": {jweToken} }}",
                              Encoding.UTF8, "application/json"));
        var dec = JsonDocument.Parse(await decResp.Content.ReadAsStringAsync()).RootElement;
        var plaintext = Encoding.UTF8.GetString(
            Convert.FromBase64String(dec.GetProperty("decryptedData").GetString()));
        Console.WriteLine($"plaintext={plaintext}");
    }
}

Why ML-KEM-512 vs 1024

AspectML-KEM-512 (Level 1)ML-KEM-1024 (Level 5)
Classical equivalent~AES-128~AES-256
Public key size~800 bytes~1568 bytes
Ciphertext size~768 bytes~1568 bytes
Encapsulation costFastest~2× slower

Pick 512 for high-throughput or bandwidth-sensitive scenarios where NIST Level 1 is acceptable per your threat model.

Where to go next


Did this page help you?