# ML-KEM (Post-Quantum)

URL: /docs/experiments/ml-kem
> Post-quantum ML-KEM-768 (Kyber) key encapsulation with Web Crypto at the edge - PQC demo for quantum-resistant key exchange
Tags: edge, crypto, post-quantum, ml-kem, kyber, pqc, web-crypto, security
Markdown: /llms.mdx/experiments/ml-kem/content.md



**ML-KEM** (formerly **Kyber**) is NIST's post-quantum key encapsulation standard. This Worker demonstrates **ML-KEM-768** with the Web Crypto API on Cloudflare Workers: generate a key pair, encapsulate a shared secret, and decapsulate to verify - a pasteable PQC reference for quantum-resistant key exchange at the edge.

## API Reference [#api-reference]

### `GET /keygen` [#get-keygen]

Generate an ML-KEM-768 key pair, encapsulate to a ciphertext, and decapsulate to verify that the shared secrets match.

**Parameters:** None

**Example request**

```bash
curl https://ml-kem.example.workers.dev/keygen
```

**Example response**

```json
{
  "algorithm": "ML-KEM-768",
  "publicKey": "a1b2c3d4...",
  "ciphertext": "e5f6g7h8...",
  "encapsulatedSecret": "i9j0k1l2...",
  "decapsulatedSecret": "i9j0k1l2...",
  "secretsMatch": true,
  "publicKeyLength": 1184,
  "ciphertextLength": 1088,
  "sharedSecretLength": 32
}
```

**Fields**

* **`algorithm`** `string` - The ML-KEM variant used (ML-KEM-768)
* **`publicKey`** `string` - Hex-encoded public key
* **`ciphertext`** `string` - Hex-encoded ciphertext from encapsulation
* **`encapsulatedSecret`** `string` - Truncated hex-encoded shared secret (first 32 chars + "...")
* **`decapsulatedSecret`** `string` - Truncated hex-encoded shared secret from decapsulation
* **`secretsMatch`** `boolean` - `true` when encapsulated and decapsulated secrets match
* **`publicKeyLength`** `number` - Public key size in bytes (1184 for ML-KEM-768)
* **`ciphertextLength`** `number` - Ciphertext size in bytes (1088 for ML-KEM-768)
* **`sharedSecretLength`** `number` - Shared secret size in bytes (32)

## Use Cases [#use-cases]

* Demonstrating post-quantum cryptography at the edge
* Testing ML-KEM-768 implementation for migration planning
* Building quantum-resistant key exchange demos
* Educational exploration of NIST post-quantum standards

## Limitations [#limitations]

* Uses the `webcrypto_modern_algorithms` compatibility flag (may not be available in all environments)
* Generates a fresh key pair on every request (stateless demo)
* Shared secrets are truncated in response for readability
* No persistent storage or key management

## Use in your project [#use-in-your-project]

<UseInYourProject experiment="ml-kem" platform="[&#x22;Web Crypto (`crypto.subtle`)&#x22;, &#x22;`webcrypto_modern_algorithms` flag&#x22;]" files="[&#x22;src/lib/mlkem.ts&#x22;, &#x22;examples/usage.ts&#x22;]" sources="[{&#x22;path&#x22;:&#x22;src/lib/mlkem.ts&#x22;,&#x22;title&#x22;:&#x22;mlkem.ts&#x22;,&#x22;lang&#x22;:&#x22;ts&#x22;,&#x22;code&#x22;:&#x22;export async function generateKeyPairAndTest() {\n  const keyPair = (await crypto.subtle.generateKey(\n    {\n      name: \&#x22;ML-KEM\&#x22;,\n      namedCurve: \&#x22;ML-KEM-768\&#x22;,\n    },\n    true,\n    [\&#x22;deriveBits\&#x22;]\n  )) as CryptoKeyPair;\n\n  const publicKeyExported = await crypto.subtle.exportKey(\&#x22;raw\&#x22;, keyPair.publicKey);\n  const publicKeyHex = arrayBufferToHex(publicKeyExported);\n\n  const { ciphertext, sharedSecret: encapsulatedSecret } = await crypto.subtle.encapsulate(\n    keyPair.publicKey\n  );\n\n  const ciphertextHex = arrayBufferToHex(ciphertext);\n  const encapsulatedSecretHex = arrayBufferToHex(encapsulatedSecret);\n\n  const decapsulatedSecret = await crypto.subtle.decapsulate(keyPair.privateKey, ciphertext);\n\n  const decapsulatedSecretHex = arrayBufferToHex(decapsulatedSecret);\n\n  const secretsMatch = encapsulatedSecretHex === decapsulatedSecretHex;\n\n  return {\n    algorithm: \&#x22;ML-KEM-768\&#x22;,\n    publicKey: publicKeyHex,\n    ciphertext: ciphertextHex,\n    encapsulatedSecret: encapsulatedSecretHex.substring(0, 32) + \&#x22;...\&#x22;,\n    decapsulatedSecret: decapsulatedSecretHex.substring(0, 32) + \&#x22;...\&#x22;,\n    secretsMatch,\n    publicKeyLength: publicKeyExported.byteLength,\n    ciphertextLength: ciphertext.byteLength,\n    sharedSecretLength: decapsulatedSecret.byteLength,\n  };\n}\n\nfunction arrayBufferToHex(buffer: ArrayBuffer): string {\n  return Array.from(new Uint8Array(buffer))\n    .map((b) => b.toString(16).padStart(2, \&#x22;0\&#x22;))\n    .join(\&#x22;\&#x22;);\n}&#x22;},{&#x22;path&#x22;:&#x22;examples/usage.ts&#x22;,&#x22;title&#x22;:&#x22;usage.ts&#x22;,&#x22;lang&#x22;:&#x22;ts&#x22;,&#x22;code&#x22;:&#x22;/**\n * Minimal paste example for docs (\&#x22;Use in your project\&#x22;).\n * Not part of the Worker runtime — copy into your own project.\n */\nimport { generateKeyPairAndTest } from \&#x22;../src/lib/mlkem\&#x22;;\n\nexport default {\n  async fetch(): Promise<Response> {\n    const result = await generateKeyPairAndTest();\n    return Response.json(result);\n  },\n};&#x22;}]" />

## Deployment [#deployment]

<Steps>
  <Step>
    ### Deploy to Cloudflare Workers [#deploy-to-cloudflare-workers]

    [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/ml-kem)

    The Worker is live once deployment succeeds.
  </Step>

  <Step>
    ### Test the endpoint [#test-the-endpoint]

    ```bash
    curl https://ml-kem.your-subdomain.workers.dev/keygen
    ```

    You should see a JSON response with `secretsMatch: true`.
  </Step>
</Steps>

## Local Development [#local-development]

```bash
cd apps/experiments/ml-kem
npm install
npm run dev
```

The Worker runs at `http://localhost:8787`.

**Test locally**

```bash
curl http://localhost:8787/keygen
```

## Configuration [#configuration]

### Compatibility flags [#compatibility-flags]

This experiment requires the `webcrypto_modern_algorithms` flag in `wrangler.json`:

```json
{
  "compatibility_flags": ["webcrypto_modern_algorithms"]
}
```

No bindings or secrets required.

## Cloudflare Features Used [#cloudflare-features-used]

* [Workers](https://developers.cloudflare.com/workers/) - Edge compute platform
* [Web Crypto API](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/) - Cryptographic operations at the edge
* ML-KEM-768 post-quantum key encapsulation (via `webcrypto_modern_algorithms` flag)

## Next Steps [#next-steps]

<Cards>
  <Card title="Crypto Hash" href="/experiments/crypto-hash" description="SHA digests with Web Crypto" />

  <Card title="JWT Inspector" href="/experiments/jwt-inspector" description="Decode and verify JWTs" />

  <Card title="One-Time Secret" href="/experiments/one-time-secret" description="Self-destructing secrets with AES-GCM" />
</Cards>
