Skip to main content

Encrypt Data with KEM

This guide walks you through encrypting data using Qpher hybrid KEM-DEM encryption, which combines Kyber768 post-quantum key encapsulation with AES-256-GCM symmetric encryption.

key_version is optional — pinning it requires the active version

key_version is now optional on encrypt — omit it to use your tenant's active key (the response returns the version that was used). If you pin a specific key_version, it must reference an active key — a new tenant's active key is usually higher than 1, so copying 1 verbatim returns ERR_NOT_FOUND_001. Find your active version with GET /api/v1/kms/keys/active?algorithm=Kyber768 (use algorithm=Dilithium3 for signing). Decrypt still requires the exact key_version that was used to encrypt.

Prerequisites​

  • A Qpher account with an active API key
  • At least one active Kyber768 key pair (see Key Management), or an active X-Wing key pair for hybrid encryption
  • The key_version of the active key you want to use

How Qpher KEM Encryption Works​

Qpher uses a hybrid KEM-DEM (Key Encapsulation Mechanism - Data Encapsulation Mechanism) scheme:

  1. KEM step: Kyber768 encapsulates a shared secret using your public key
  2. KDF step: HKDF-SHA256 derives a 256-bit symmetric key from the shared secret
  3. DEM step: AES-256-GCM encrypts your plaintext with the derived key

The shared secret is never stored or returned. Your private key is generated and used only inside Qpher's isolated key service (KMS-Orchestrator) and is never exported.

1088B
KEM Ciphertext (1088B)IV (Nonce) (12B)AES Ciphertext (21B)Auth Tag (16B)
Total: 1137 bytes (overhead + plaintext)

Step 1: Prepare Your Plaintext​

Your plaintext must be base64-encoded before sending it to the API.

Base64 Encoding Required

All plaintext data must be base64-encoded in the request body. The API will reject raw binary or unencoded strings. Maximum plaintext size is 1 MB (1,048,576 bytes) before encoding.

Request size

plaintext is capped at ~1 MB (1,400,000 base64 characters). For larger data, don't send it through /kem/encrypt — use key-wrap (/kem/key/wrap) or the client-side encryption pattern (encapsulate + local AES-256-GCM) so only a small wrapped key / shared secret transits the API, not your data.

Step 2: Send the Encrypt Request​

POST/api/v1/kem/encryptEncrypt plaintext using Kyber768 hybrid KEM-DEM
Encrypt Data
curl -X POST https://api.qpher.ai/api/v1/kem/encrypt \
  -H "Content-Type: application/json" \
  -H "x-api-key: qph_your_key_here" \
  -d '{
    "plaintext": "SGVsbG8sIFdvcmxkIQ=="
  }'

Using X-Wing Hybrid KEM (Pro/Enterprise)​

X-Wing Hybrid KEM (Pro/Enterprise)

For defense-in-depth, add "algorithm": "X-Wing" to combine X25519 classical key exchange with ML-KEM-768. This ensures security even if a breakthrough in lattice cryptanalysis is discovered. Requires an active X-Wing key pair — see Hybrid Cryptography.

Encrypt with X-Wing Hybrid KEM
curl -X POST https://api.qpher.ai/api/v1/kem/encrypt \
  -H "Content-Type: application/json" \
  -H "x-api-key: qph_your_key_here" \
  -d '{
    "plaintext": "SGVsbG8sIFdvcmxkIQ==",
    "key_version": 1,
    "algorithm": "X-Wing"
  }'

Step 3: Understand the Response​

RequestPOST/api/v1/kem/encrypt
Content-Type: application/json
x-api-key: qph_your_key_here
{
  "plaintext": "SGVsbG8sIFdvcmxkIQ==",
  "key_version": 1
}
Response200
{
  "data": {
    "ciphertext": "base64-encoded-ciphertext...",
    "key_version": 1,
    "algorithm": "Kyber768"
  },
  "request_id": "550e8400-e29b-41d4-a716-446655440000",
  "timestamp": "2026-02-15T10:30:00Z"
}
FieldDescription
ciphertextBase64-encoded ciphertext containing the KEM ciphertext, IV, AES ciphertext, and auth tag
key_versionThe key version used for encryption — store this alongside the ciphertext
algorithmThe KEM algorithm used (Kyber768 or X-Wing)
request_idUnique request identifier for tracing and support
Store the key_version returned by encrypt

When you encrypt, the response includes the key_version that was used (resolved from your tenant's active key if you omitted it). Always store that returned key_version alongside the ciphertext — you will need the exact same version to decrypt later. You can omit key_version on encrypt to use the active key, or pass it explicitly to pin a version. Decrypt always requires the exact key_version that was used to encrypt.

Error Handling​

HTTP StatusError CodeCauseResolution
400ERR_INVALID_001Missing or invalid plaintext or key_versionEnsure plaintext is valid base64 and key_version is a positive integer
400ERR_KEM_001Plaintext exceeds 1 MB limitSplit data into smaller chunks
401ERR_AUTH_001Invalid or missing API keyCheck your x-api-key header
404ERR_KEM_003Key version not foundVerify the key_version exists for your tenant
409ERR_KEM_004Key is not in active statusOnly active keys can encrypt — generate or rotate to get a new active key
429ERR_RATE_001Rate limit exceededReduce request frequency or upgrade your plan