Runtime Algorithm Discovery & PQC Smoke-Test
Query the catalogue of supported algorithms at runtime, filter PQC-only, and smoke-test the first result end-to-end. Useful for feature flags and tenant provisioning tooling.
🔍 Runtime Algorithm Discovery & PQC Smoke-Test
Scenario: discover the algorithms the platform supports at runtime (via GET /api/v3/algorithms), filter to post-quantum only, pick the first ENCRYPT_DECRYPT result and prove it works with a full round-trip. Useful for feature flags ("only offer PQC in the UI if it's really available") and for automated provisioning tooling.
Prerequisites
Same two identities as the AES-256 recipe.
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="••••••••••••••••••••••••••••••••"
# Step 1: both tokens (see aes-256-basic for the full boilerplate)
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)
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: discover PQC algorithms (public endpoint, uses app token)
curl -s "$BASE_URL/api/v3/key-management/supported-algorithms?category=POST_QUANTUM&keyOps=encrypt,decrypt" \
-H "Authorization: Bearer $APP_TOKEN" \
| tee /tmp/algos.json | jq '.[] | {kty, alg, status, securityLevel}'
# Step 3: pick the first PQC KEM candidate
export PICK_KTY=$(jq -r '.[0].kty' /tmp/algos.json) # e.g. "ML-KEM"
export PICK_ALG=$(jq -r '.[0].alg' /tmp/algos.json) # e.g. "ML-KEM-768"
export KID="smoketest-$(date +%s)"
echo "Picked kty=$PICK_KTY alg=$PICK_ALG kid=$KID"
# Step 4: admin creates a key with the picked algorithm
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\":\"$PICK_KTY\", \"algorithm\":\"$PICK_ALG\",
\"purpose\":\"ENCRYPT_DECRYPT\", \"keyOps\":[\"encrypt\",\"decrypt\"]
}" | jq
# Step 5: smoke test — encrypt / decrypt round-trip (app)
export DATA_B64=$(printf "%s" "PQC smoke test" | base64 -w0)
enc=$(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\" }")
echo "Encrypted with: $(echo "$enc" | jq -r .algorithmUsed)"
dec=$(curl -s -X POST "$BASE_URL/api/v3/crypto/decrypt" \
-H "Authorization: Bearer $APP_TOKEN" -H "Content-Type: application/json" \
-d "{ \"jweToken\": $(echo "$enc" | jq .jweToken) }")
echo "Decrypted: $(echo "$dec" | jq -r .decryptedData | base64 -d)"
echo "PQC smoke test PASSED"import base64, requests, time
BASE = "https://staging.ankatech.co"
TENANT_ID = "00000000-0000-0000-0000-000000000003"
def tok(f): return requests.post(f"{BASE}/api/v3/auth/token", data=f).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':'…'})}"}
# Discover PQC algorithms
algos = requests.get(
f"{BASE}/api/v3/key-management/supported-algorithms",
params={"category": "POST_QUANTUM", "keyOps": "encrypt,decrypt"},
headers=app,
).json()
# Pick the first candidate
pick = algos[0]
kid = f"smoketest-{int(time.time())}"
print(f"Picked kty={pick['kty']} alg={pick['alg']} kid={kid}")
# Admin creates the key
requests.post(f"{BASE}/api/v3/admin/tenants/{TENANT_ID}/keys", headers=admin, json={
"kid": kid, "kty": pick["kty"], "algorithm": pick["alg"],
"purpose": "ENCRYPT_DECRYPT", "keyOps": ["encrypt","decrypt"],
}).raise_for_status()
# Smoke test round-trip
data_b64 = base64.b64encode(b"PQC smoke test").decode()
enc = requests.post(f"{BASE}/api/v3/crypto/encrypt", headers=app,
json={"kid": kid, "data": data_b64}).json()
dec = requests.post(f"{BASE}/api/v3/crypto/decrypt", headers=app,
json={"jweToken": enc["jweToken"]}).json()
assert base64.b64decode(dec["decryptedData"]) == b"PQC smoke test"
print(f"PQC smoke test PASSED — algorithm={enc['algorithmUsed']}")import java.net.URI;
import java.net.http.*;
import java.util.Base64;
import com.fasterxml.jackson.databind.*;
public class AlgorithmDiscovery {
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 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);
// Discover PQC algorithms
var algosResp = http.send(HttpRequest.newBuilder(URI.create(
BASE_URL + "/api/v3/key-management/supported-algorithms"
+ "?category=POST_QUANTUM&keyOps=encrypt,decrypt"))
.header("Authorization", appAuth).GET().build(),
HttpResponse.BodyHandlers.ofString());
JsonNode algos = json.readTree(algosResp.body());
JsonNode pick = algos.get(0);
String kid = "smoketest-" + System.currentTimeMillis();
System.out.println("Picked kty=" + pick.get("kty").asText()
+ " alg=" + pick.get("alg").asText() + " kid=" + kid);
// Admin creates the key
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(String.format(
"{ \"kid\": \"%s\", \"kty\": \"%s\", \"algorithm\": \"%s\","
+ " \"purpose\": \"ENCRYPT_DECRYPT\", \"keyOps\": [\"encrypt\",\"decrypt\"] }",
kid, pick.get("kty").asText(), pick.get("alg").asText()))).build(),
HttpResponse.BodyHandlers.ofString());
// Smoke test round-trip
var dataB64 = Base64.getEncoder().encodeToString("PQC smoke test".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());
JsonNode enc = json.readTree(encResp.body());
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());
String plaintext = new String(Base64.getDecoder().decode(
json.readTree(decResp.body()).get("decryptedData").asText()));
if (!"PQC smoke test".equals(plaintext)) throw new RuntimeException("Mismatch");
System.out.println("PQC smoke test PASSED — algorithm=" + enc.get("algorithmUsed").asText());
}
}const BASE_URL = "https://staging.ankatech.co";
const TENANT_ID = "00000000-0000-0000-0000-000000000003";
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" });
// Discover PQC algorithms (app)
const url = new URL(`${BASE_URL}/api/v3/key-management/supported-algorithms`);
url.searchParams.set("category", "POST_QUANTUM");
url.searchParams.set("keyOps", "encrypt,decrypt");
const algos = await (await fetch(url, { headers: app })).json();
// Pick the first candidate
const pick = algos[0];
const kid = `smoketest-${Date.now()}`;
console.log(`Picked kty=${pick.kty} alg=${pick.alg} kid=${kid}`);
// Admin creates the key
await fetch(`${BASE_URL}/api/v3/admin/tenants/${TENANT_ID}/keys`, {
method: "POST", headers: jsonH(admin),
body: JSON.stringify({
kid, kty: pick.kty, algorithm: pick.alg,
purpose: "ENCRYPT_DECRYPT", keyOps: ["encrypt","decrypt"],
}),
});
// Smoke test round-trip
const dataB64 = Buffer.from("PQC smoke test").toString("base64");
const enc = await (await fetch(`${BASE_URL}/api/v3/crypto/encrypt`, {
method: "POST", headers: jsonH(app),
body: JSON.stringify({ kid, data: dataB64 }),
})).json();
const dec = await (await fetch(`${BASE_URL}/api/v3/crypto/decrypt`, {
method: "POST", headers: jsonH(app),
body: JSON.stringify({ jweToken: enc.jweToken }),
})).json();
if (Buffer.from(dec.decryptedData, "base64").toString() !== "PQC smoke test")
throw new Error("Mismatch");
console.log(`PQC smoke test PASSED — algorithm=${enc.algorithmUsed}`);using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
class AlgorithmDiscovery
{
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 = "••••••••••••••••••••••••••••••••";
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}");
// Discover PQC algorithms
var algosResp = await appHttp.GetAsync(
$"{BASE_URL}/api/v3/key-management/supported-algorithms"
+ "?category=POST_QUANTUM&keyOps=encrypt,decrypt");
var algos = JsonDocument.Parse(await algosResp.Content.ReadAsStringAsync()).RootElement;
var pick = algos[0];
var kid = $"smoketest-{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}";
Console.WriteLine($"Picked kty={pick.GetProperty("kty").GetString()}"
+ $" alg={pick.GetProperty("alg").GetString()} kid={kid}");
// Admin creates the key
await adminHttp.PostAsync(
$"{BASE_URL}/api/v3/admin/tenants/{TENANT_ID}/keys",
new StringContent(JsonSerializer.Serialize(new {
kid, kty = pick.GetProperty("kty").GetString(),
algorithm = pick.GetProperty("alg").GetString(),
purpose = "ENCRYPT_DECRYPT", keyOps = new[] { "encrypt", "decrypt" },
}), Encoding.UTF8, "application/json"));
// Smoke test round-trip
var dataB64 = Convert.ToBase64String(Encoding.UTF8.GetBytes("PQC smoke test"));
var encResp = await appHttp.PostAsync(
$"{BASE_URL}/api/v3/crypto/encrypt",
new StringContent(JsonSerializer.Serialize(new { kid, data = dataB64 }),
Encoding.UTF8, "application/json"));
var enc = JsonDocument.Parse(await encResp.Content.ReadAsStringAsync()).RootElement;
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()));
if (plaintext != "PQC smoke test") throw new Exception("Mismatch");
Console.WriteLine($"PQC smoke test PASSED — algorithm={enc.GetProperty("algorithmUsed").GetString()}");
}
}Query parameter cheat-sheet
GET /api/v3/key-management/supported-algorithms accepts:
| Param | Values | Example |
|---|---|---|
category | CLASSICAL, POST_QUANTUM, HYBRID | ?category=POST_QUANTUM |
status | RECOMMENDED, EXPERIMENTAL, LEGACY | ?status=RECOMMENDED |
minSecurityLevel | integer 1-5 | ?minSecurityLevel=3 |
maxSecurityLevel | integer 1-5 | ?maxSecurityLevel=5 |
keyOps | comma list — algo must support ALL | ?keyOps=sign,verify |
standards | comma list — NIST, BSI, ANSSI, ENISA, ISO | ?standards=NIST |
kty | comma list — matches ANY | ?kty=ML-KEM,ML-DSA |
alg | comma list — matches ANY | ?alg=ML-KEM-768,ML-KEM-1024 |
Where to go next
GET /api/v3/algorithms— endpoint reference with full response schema.- ML-KEM-1024 Encrypt / Decrypt — hard-coded PQC recipe if you know your target algorithm.
Updated 10 days ago
Did this page help you?