Kyndex

Handling Client Key Material

Which private keys the client decrypts after login, how encrypted key blobs are protected, and what key material must stay memory-only.

Literal stores private key material only as encrypted blobs. Clients decrypt those blobs locally with the User Master Key after login. The User Master Key is derived on the client and is never sent to Literal.

Literal clients handle three categories of keypairs: account encryption keys, account signing keys, and per-entity delivery keys.

The encryption and signing keypairs are account-level. Delivery keypairs are derived per entity membership.

Key Material Overview

Key pairFieldsPublic key sizePurpose
Encryption keypairmlkem_public_key + x25519_public_key1568 + 32 bytesReceiving encrypted grant payloads; entity key wrapping
Signing keypairsigning_public_key1984 bytesProving identity at membership claim; capability token signatures
Delivery keypairdelivery_mlkem_ek + delivery_dsa_vk1600 + 1984 bytesPer-entity receiving address for admin document deliveries

The encryption and signing keypairs are registered once at account creation and persist for the account lifetime. Delivery keypairs are registered per entity when a member claims membership.

Decrypting Key Material After Login

The login response includes two encrypted private key blobs:

  • user.mlkem_private_encrypted — the ML-KEM and X25519 private keys, encrypted with the User Master Key.
  • user.signing_private_encrypted — the combined signing private key, encrypted with the User Master Key.

To decrypt either blob, reconstruct the AEAD authentication data from three values in the same login response:

AAD componentSourcePer-key value
User identifieruser.idSame for both blobs
Key versionuser.key_versionSame for both blobs
Key type identifiermlkem_dk for the encryption keypair, signing_sk for the signing keypair

Include all three components in the AEAD authentication data when decrypting. Any mismatch — including a stale key_version from before an account recovery — produces an authentication failure rather than silently decrypting stale material.

Do not guess or reuse AAD from another key type. The key type identifier is part of the authentication boundary.

key_version increments each time account recovery completes. The server always returns the current key_version and freshly re-encrypted blobs in the login response. Always use the key_version from the current login response, not a cached value from a previous session.

Account Encryption Keypair

The encryption keypair combines two independent algorithms:

  • ML-KEM-1024 — post-quantum key encapsulation, 1568-byte public key.
  • X25519 — classical elliptic-curve Diffie–Hellman, 32-byte public key.

They are stored as two separate fields in the API. A sender uses both public keys when constructing a hybrid encapsulation, reducing reliance on either cryptographic family alone.

At registration — the client generates both keypairs as a single hybrid keypair. The two public keys are uploaded separately; the private keys are stored together in a single encrypted blob:

FieldDescription
mlkem_public_keyML-KEM-1024 public key, 1568 bytes (base64).
x25519_public_keyX25519 public key, 32 bytes (base64).
mlkem_private_encryptedBoth ML-KEM and X25519 private keys, encrypted with the User Master Key (base64).

The server stores the public keys in plaintext (they are public) and the encrypted blob. It never sees either private key.

At loginmlkem_private_encrypted is returned in the login response. See Decrypting Key Material After Login.

When a sender constructs a grant — the sender fetches the recipient’s public encryption keys from GET /v1/users/{userId}/public-keys and uses them to encapsulate a shared secret. The signing public key is not returned from that endpoint. See Creating And Claiming Grants.

Account Signing Keypair

The signing keypair is a single combined key:

  • ML-DSA-65 + Ed25519 — hybrid digital signature scheme, 1984-byte combined public key.

Literal requires both signature layers to verify before accepting the proof, reducing reliance on a single signature scheme.

At registration — the client generates the combined keypair and uploads:

FieldDescription
signing_public_keyCombined ML-DSA-65 + Ed25519 public key, 1984 bytes (base64).
signing_private_encryptedCombined signing private key, encrypted with the User Master Key (base64).

At loginsigning_private_encrypted is returned in the login response. See Decrypting Key Material After Login.

The signing private key is used when:

  • A member claims entity membership — the client signs a canonical message to prove possession of the key the entity admin committed to at invite time.
  • The client produces capability tokens for grant and delivery operations.

Per-Entity Delivery Keypair

Delivery keys are distinct from the account-wide encryption and signing keypairs. Each entity membership has its own delivery keypair derived deterministically from the member’s blind index key (BIK) and the entity ID — not randomly generated. Because delivery keys are derived, the client does not need to store them: rederive from BIK and the entity ID whenever needed.

At membership claim — the client derives both delivery keys and registers them:

FieldDescription
delivery_mlkem_ekPer-entity ML-KEM-1024 encapsulation key, 1600 bytes (base64).
delivery_dsa_vkPer-entity hybrid DSA verifying key, 1984 bytes (base64).

After membership claim, delivery public keys are stored on the membership record so admins can address encrypted deliveries to that member.

Because delivery keys are derived deterministically, revoking a membership does not require any key rotation — the derived key simply becomes unreachable through a revoked membership.

See Entity Deliveries for the full delivery workflow.

Security Obligations

MaterialRequirement
User Master KeyDerive in memory from password + encryption_salt on each login. Never persist.
ML-KEM + X25519 private keysMemory only — decrypt from mlkem_private_encrypted using the User Master Key; discard on logout.
Signing private keyMemory only — decrypt from signing_private_encrypted using the User Master Key; discard on logout.
mlkem_private_encrypted, signing_private_encryptedMay be cached locally in encrypted form only. Received fresh on each login.
Recovery keyPlaintext recovery key should be stored outside Literal and separately from the account password. recovery_key_encrypted may be cached encrypted.
Delivery private keysRederive on demand from BIK + entity ID. No storage required.

Last updated on

On this page