Skip to content

Migrating from RSA-2048 to ML-KEM-1024 (streaming JWET)

This walkthrough shows how to upgrade existing ciphertext from RSA-2048 to ML-KEM-1024 with zero plaintext exposure.
All cryptographic steps run in streaming mode using the high-throughput Secure Streaming endpoints.


1 · Process overview

# Action Endpoint Payload format
1 Generate RSA-2048 key POST /api/key-management/keys JSON
2 Encrypt file (RSA) POST /api/crypto/stream/encrypt multipart/form-dataJWET
3 Generate ML-KEM-1024 key POST /api/key-management/keys JSON
4 Re-encrypt JWET (RSA → ML-KEM) POST /api/crypto/stream/reencrypt multipart/form-data (JWET header + ciphertext)
5 Decrypt JWET (ML-KEM) POST /api/crypto/stream/decrypt multipart/form-data
flowchart TD
    Start(("Start")) --> K1[Generate RSA-2048 key]
    K1 --> ENC["Encrypt file → JWET (RSA)"]
    ENC --> K2[Generate ML-KEM-1024 key]
    K2 --> REENC["Re-encrypt JWET (RSA → ML-KEM)"]
    REENC --> DEC["Decrypt JWET (ML-KEM)"]
    DEC --> End(("End"))

2 - Detailed steps

2.1 Generate RSA-2048 key

  • EndpointPOST /api/key-management/keys

  • Body application/json

{
  "kid": "rsa_2048_key_001",
  "kty": "RSA",
  "alg": "RSA-2048",
  "keyOps": ["encrypt", "decrypt"],
  "exportable": true
}

Response 201 Created


2.2 Encrypt file (streaming, RSA)

  • EndpointPOST /api/crypto/stream/encrypt

  • Body multipart/form-data

Part Name Content-Type Notes
1 metadata application/json { "kid": "rsa_2048_key_001" }
2 file application/octet-stream Raw plaintext stream

Result

  • multipart/mixed stream containing:

    1. JWET General JSON header (RSA recipient)

    2. Ciphertext block(s) + tag

  • Headers:

X-Key-Requested: rsa_2048_key_001
X-Key-Used: rsa_2048_key_001
X-Algorithm-Used: RSA-2048+A256GCM

2.3 Generate ML-KEM-1024 key

  • EndpointPOST /api/key-management/keys

  • Body application/json

{
  "kid": "mlkem_1024_key_001",
  "kty": "ML-KEM",
  "alg": "ML-KEM-1024",
  "keyOps": ["encrypt", "decrypt"],
  "exportable": true
}

Response 201 Created


2.4 Re-encrypt JWET (RSA → ML-KEM)

  • EndpointPOST /api/crypto/stream/reencrypt?newKid=mlkem_1024_key_001

  • Body multipart/form-data

Part Name Content-Type Notes
1 metadata application/json JWET header (JSON) -- may include or omit kid
2 file application/octet-stream Ciphertext (JWET body)

Result

  • multipart/mixed stream containing a new JWET whose recipient uses ML-KEM-1024.

  • Lifecycle headers:

X-OldKey-Requested: rsa_2048_key_001
X-OldKey-Used: rsa_2048_key_001
X-NewKey-Requested: mlkem_1024_key_001
X-NewKey-Used: mlkem_1024_key_001
X-OldKey-Algorithm-Used: RSA-2048+A256GCM
X-NewKey-Algorithm-Used: ML-KEM-1024+A256GCM

If kid was absent in the incoming JWET header, the server adds:

X-Migration-Mode: true

2.4.1 · metadata part anatomy

The first form-data part is a JWET header in General-JSON serialization that conforms to the component
#/components/schemas/JwetGeneralJsonHeaderReencrypt.

Field Type Required Description
protected string Yes Base64url-encoded protected header (alg, enc, typ, etc.).
iv string Yes Base64url-encoded IV for the AEAD content-encryption algorithm.
ciphertext string \| null No (always null) Detached ciphertext placeholder.
tag string \| null No (always null) Detached authentication tag placeholder.
recipients[0].header.alg string Yes Key-encapsulation / wrap algorithm (e.g. RSA-OAEP-256).
recipients[0].header.kid string \| null Conditional Managed flow: present → used as oldKid.
Migration flow: absent → caller must supply sourceKidOverride.
recipients[0].encrypted_key string Yes Base64url-encoded encrypted CEK for this recipient.

Exactly one recipient is permitted; the server rejects additional entries with 400 /errors/invalid-jwet.

Managed flow (kid present)
{
  "protected": "eyJhbGciOiJSQS1PQUVQLTI1NiIsImVuYyI6IkEyNTZHQ00ifQ",
  "iv": "QE-6gFcGy4B9yaQw",
  "ciphertext": null,
  "tag": null,
  "recipients": [
    {
      "header": { "alg": "RSA-OAEP-256", "kid": "rsa_2048_key_001" },
      "encrypted_key": "uJrM_6C5x..."
    }
  ]
}
Migration flow (kid absent + sourceKidOverride)

{
  "protected": "eyJhbGciOiJSQS1PQUVQLTI1NiIsImVuYyI6IkEyNTZHQ00ifQ",
  "iv": "QE-6gFcGy4B9yaQw",
  "ciphertext": null,
  "tag": null,
  "recipients": [
    {
      "header": { "alg": "RSA-OAEP-256" },
      "encrypted_key": "uJrM_6C5x..."
    }
  ]
}
Query string adds: ?newKid=mlkem_1024_key_001&sourceKidOverride=rsa_2048_key_001


2.5 Decrypt JWET (ML-KEM)

  • EndpointPOST /api/crypto/stream/decrypt

  • Body multipart/form-data

Part Name Content-Type Notes
1 metadata application/json { "kid": "mlkem_1024_key_001" }
2 file application/octet-stream JWET header + ciphertext

Result

  • Plaintext stream (application/octet-stream)

  • Headers:

X-Key-Requested: mlkem_1024_key_001
X-Key-Used: mlkem_1024_key_001
X-Algorithm-Used: ML-KEM-1024+A256GCM

2.5.1 · metadata part anatomy

The first form-data part is a JWET header in General-JSON format that satisfies the component
#/components/schemas/JwetGeneralJsonHeaderDecrypt.

Field Type Required Description
protected string Yes Base64url-encoded protected header containing alg, enc, typ, etc.
iv string Yes Base64url-encoded IV for the AEAD content-encryption algorithm.
ciphertext string \| null No (always null) Detached ciphertext placeholder.
tag string \| null No (always null) Detached authentication tag placeholder.
recipients[0].header.alg string Yes Key-encapsulation / wrap algorithm (e.g. ML-KEM-1024).
recipients[0].header.kid string Yes Required – identifies the decryption key (mlkem_1024_key_001).
recipients[0].encrypted_key string Yes Base64url-encoded encrypted CEK for this recipient.

Exactly one recipient is permitted; more than one triggers 400 /errors/invalid-jwet.

Example (managed decryption flow)
{
  "protected": "eyJhbGciOiJNTC1LRU0tMTAyNCIsImVuYyI6IkEyNTZHQ00ifQ",
  "iv": "Fq_odhuz_50HZBW-",
  "ciphertext": null,
  "tag": null,
  "recipients": [
    {
      "header": { "alg": "ML-KEM-1024", "kid": "mlkem_1024_key_001" },
      "encrypted_key": "sbM_9XtE..."
    }
  ]
}

3 - Key considerations

  1. No plaintext disclosure -- data is decrypted and re-encrypted in-memory; nothing hits disk.

  2. Constant-memory streaming -- supports multi-gigabyte blobs without RAM spikes.

  3. Quantum-safe target -- ML-KEM-1024 provides security against future quantum adversaries.

  4. Lifecycle telemetry -- header set gives full visibility into rotation status and algorithm lineage.

  5. Automate rotations -- schedule periodic JWET re-encrypt jobs to keep ahead of key expiry and cryptanalytic advances.


Document version 2.2.0 -- generated from OpenAPI build 2025-05-31