RSA-2048 → ML-KEM-1024 Token Rotation (highest PQC level)

Rotate an RSA-2048 key into ML-KEM-1024 (NIST Level 5 PQC). Same transparent redirect as the ML-KEM-768 variant, but targeting the highest PQC tier.

🔀 RSA-2048 → ML-KEM-1024 Token Rotation (highest PQC level)

Scenario: identical to the RSA → ML-KEM Rotation recipe, except the successor uses ML-KEM-1024 (NIST FIPS 203, security Level 5) instead of ML-KEM-768 (Level 3). Choose this variant when your threat model demands the highest post-quantum tier available today.

Prerequisites

Same two identities as the AES-256 recipe.


Step-by-step

export BASE_URL="https://staging.ankatech.co"
export TENANT_ID="00000000-0000-0000-0000-000000000003"
export SRC_KID="rsa2048-payments-src"
export SUCC_KID="mlkem1024-payments-succ"
# ADMIN_TOKEN + APP_TOKEN as in AES-256 recipe

# Step 2: admin creates RSA-2048 source
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\":\"$SRC_KID\", \"kty\":\"RSA\", \"algorithm\":\"RSA-OAEP-256\",
        \"purpose\":\"ENCRYPT_DECRYPT\", \"keyOps\":[\"encrypt\",\"decrypt\"] }" | jq

# Step 3: admin rotates to ML-KEM-1024 successor
curl -s -X POST "$BASE_URL/api/v3/admin/tenants/$TENANT_ID/keys/$SRC_KID/rotations" \
  -H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" \
  -d "{
    \"newKey\": {
      \"kid\":\"$SUCC_KID\", \"kty\":\"ML-KEM\", \"algorithm\":\"ML-KEM-1024\",
      \"purpose\":\"ENCRYPT_DECRYPT\", \"keyOps\":[\"encrypt\",\"decrypt\"]
    },
    \"acknowledgeCapabilityReduction\": true
  }" | jq

# Step 4-5: app encrypts+decrypts using ORIGINAL kid — transparent PQC redirect
export DATA_B64=$(printf "%s" "Level 5 secured" | 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\":\"$SRC_KID\", \"data\":\"$DATA_B64\" }")
echo "algorithmUsed = $(echo "$enc" | jq -r .algorithmUsed)"
# → "ML-KEM-1024+A256GCM"

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) }" \
  | jq -r .decryptedData | base64 -d
# Identical to the /docs/rsa-to-ml-kem-rotation recipe.
# The only differences are on the admin's rotation call:
#   newKey.kty       = "ML-KEM"
#   newKey.algorithm = "ML-KEM-1024"   # ← Level 5 instead of ML-KEM-768 (Level 3)
# The application code is unchanged.
import java.net.URI;
import java.net.http.*;
import java.util.Base64;
import com.fasterxml.jackson.databind.*;

public class RsaToMlKem1024Rotation {
    static final String BASE_URL  = "https://staging.ankatech.co";
    static final String TENANT_ID = "00000000-0000-0000-0000-000000000003";
    static final String SRC_KID   = "rsa2048-payments-src";
    static final String SUCC_KID  = "mlkem1024-payments-succ";

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

    public static void main(String[] args) throws Exception {
        String adminAuth = "Bearer " + System.getenv("ADMIN_TOKEN");
        String appAuth   = "Bearer " + System.getenv("APP_TOKEN");

        // Admin creates RSA-2048 source
        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": "RSA", "algorithm": "RSA-OAEP-256",
                  "purpose": "ENCRYPT_DECRYPT", "keyOps": ["encrypt","decrypt"] }""".formatted(SRC_KID))).build(),
            HttpResponse.BodyHandlers.ofString());

        // Admin rotates to ML-KEM-1024 successor (Level 5 instead of 3)
        http.send(HttpRequest.newBuilder(URI.create(
            BASE_URL + "/api/v3/admin/tenants/" + TENANT_ID + "/keys/" + SRC_KID + "/rotations"))
            .header("Authorization", adminAuth).header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString("""
                { "newKey": { "kid": "%s", "kty": "ML-KEM", "algorithm": "ML-KEM-1024",
                              "purpose": "ENCRYPT_DECRYPT", "keyOps": ["encrypt","decrypt"] },
                  "acknowledgeCapabilityReduction": true }""".formatted(SUCC_KID))).build(),
            HttpResponse.BodyHandlers.ofString());

        // App encrypts using ORIGINAL kid — transparent PQC redirect
        var dataB64 = Base64.getEncoder().encodeToString("Level 5 secured".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\": \"" + SRC_KID + "\", \"data\": \"" + dataB64 + "\" }")).build(),
            HttpResponse.BodyHandlers.ofString());
        JsonNode enc = json.readTree(encResp.body());
        System.out.println("keyRequested=" + enc.get("keyRequested").asText()
            + "  algorithmUsed=" + enc.get("algorithmUsed").asText());
    }
}
// Identical to the /docs/rsa-to-ml-kem-rotation recipe.
// Only changes: newKey.kty="ML-KEM", newKey.algorithm="ML-KEM-1024" on the rotation call.
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

class RsaToMlKem1024Rotation
{
    const string BASE_URL  = "https://staging.ankatech.co";
    const string TENANT_ID = "00000000-0000-0000-0000-000000000003";
    const string SRC_KID   = "rsa2048-payments-src";
    const string SUCC_KID  = "mlkem1024-payments-succ";

    static async Task Main()
    {
        var adminHttp = new HttpClient();
        adminHttp.DefaultRequestHeaders.Add("Authorization",
            $"Bearer {Environment.GetEnvironmentVariable("ADMIN_TOKEN")}");
        var appHttp = new HttpClient();
        appHttp.DefaultRequestHeaders.Add("Authorization",
            $"Bearer {Environment.GetEnvironmentVariable("APP_TOKEN")}");

        // Admin creates RSA-2048 source
        await adminHttp.PostAsync(
            $"{BASE_URL}/api/v3/admin/tenants/{TENANT_ID}/keys",
            new StringContent(JsonSerializer.Serialize(new {
                kid = SRC_KID, kty = "RSA", algorithm = "RSA-OAEP-256",
                purpose = "ENCRYPT_DECRYPT", keyOps = new[] { "encrypt", "decrypt" },
            }), Encoding.UTF8, "application/json"));

        // Admin rotates to ML-KEM-1024 successor (Level 5)
        await adminHttp.PostAsync(
            $"{BASE_URL}/api/v3/admin/tenants/{TENANT_ID}/keys/{SRC_KID}/rotations",
            new StringContent(JsonSerializer.Serialize(new {
                newKey = new {
                    kid = SUCC_KID, kty = "ML-KEM", algorithm = "ML-KEM-1024",
                    purpose = "ENCRYPT_DECRYPT", keyOps = new[] { "encrypt", "decrypt" },
                },
                acknowledgeCapabilityReduction = true,
            }), Encoding.UTF8, "application/json"));

        // App encrypts using ORIGINAL kid — transparent redirect
        var dataB64 = Convert.ToBase64String(Encoding.UTF8.GetBytes("Level 5 secured"));
        var encResp = await appHttp.PostAsync(
            $"{BASE_URL}/api/v3/crypto/encrypt",
            new StringContent(JsonSerializer.Serialize(new { kid = SRC_KID, data = dataB64 }),
                              Encoding.UTF8, "application/json"));
        var enc = JsonDocument.Parse(await encResp.Content.ReadAsStringAsync()).RootElement;
        Console.WriteLine($"keyRequested={enc.GetProperty("keyRequested").GetString()}"
            + $"  algorithmUsed={enc.GetProperty("algorithmUsed").GetString()}");
    }
}

Why ML-KEM-1024 vs ML-KEM-768

AspectML-KEM-768 (Level 3)ML-KEM-1024 (Level 5)
Classical equivalent~AES-192~AES-256
Public key size~1184 bytes~1568 bytes
Ciphertext size~1088 bytes~1568 bytes
Encapsulation costFaster~40% slower

Pick 1024 when regulatory or contractual requirements pin you to NIST Level 5.

Where to go next


Did this page help you?