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 -dimport 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
| Aspect | ML-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 cost | Fastest | ~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
- ML-KEM-1024 Encrypt / Decrypt — Level 5 counterpart.
GET /api/v3/algorithms— full catalogue.
Updated 10 days ago
Did this page help you?