Skip to main content

Core Concepts

This page explains the foundational concepts you need to understand when working with Qpher. These ideas appear throughout the API, the SDKs, and the documentation.


Tenants

Qpher is a multi-tenant platform. Every organization that signs up gets its own tenant, which serves as the top-level isolation boundary for all data and operations.

  • Each tenant has a unique Tenant ID (UUID)
  • All PQC keys, API keys, and operations are scoped to a tenant
  • Data from one tenant is never accessible to another -- this is enforced at every layer of the stack, from database queries to API routing
  • The gateway injects the tenant context into every request automatically based on your API key

You never need to pass a tenant_id in your API calls. It is derived from your API key and injected by the gateway.


API Keys

API keys authenticate your requests to Qpher. They are the primary credential for all API interactions.

Key Format

Qpher API keys follow the format qph_ followed by a random string:

qph_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6

One Key Per Environment

We recommend creating separate API keys for each environment (development, staging, production). This limits blast radius if a key is compromised and makes rotation simpler.

How Keys Are Stored

Qpher never stores your API key in plaintext. When you create a key, we compute a dual HMAC-SHA256 hash:

  1. Simple hash -- used for fast gateway lookup
  2. Scoped hash -- includes the tenant ID for verification

This dual-hash design ensures that API key resolution is both fast (sub-millisecond lookup) and secure (tenant-scoped verification prevents cross-tenant collisions).

API key security

Your full API key is shown only once at creation time. Store it in a secrets manager or environment variable. If lost, you must rotate to a new key.

Key Versioning

Each API key has a version number that increments on rotation. The version is included in audit logs so you can trace which key was used for any operation.


PQC Keys

PQC keys are the cryptographic key pairs used for quantum-resistant operations. Qpher supports two types:

PropertyKyber768Dilithium3
NIST NameML-KEM-768ML-DSA-65
NIST StandardFIPS 203FIPS 204
OperationKey Encapsulation (Encrypt/Decrypt)Digital Signatures (Sign/Verify)
Security LevelNIST Level 3 (~AES-192)NIST Level 3 (~AES-192)
Public Key Size1,184 bytes1,952 bytes
Private Key Size2,400 bytes4,000 bytes
Ciphertext Size1,088 bytes3,293 bytes
Shared Secret Size32 bytesN/A
Latency Target< 15ms (p95)< 30ms (p95)

Kyber768 Keys (KEM)

Used for key encapsulation -- the mechanism that enables quantum-safe encryption. A Kyber768 key pair consists of:

  • A public key (1,184 bytes) -- used during encryption to encapsulate a shared secret
  • A private key (2,400 bytes) -- used during decryption to recover the shared secret

Dilithium3 Keys (Signatures)

Used for digital signatures -- creating and verifying quantum-resistant signatures. A Dilithium3 key pair consists of:

  • A public key (1,952 bytes) -- used to verify signatures
  • A private key (4,000 bytes) -- used to create signatures

Key Versions

Every PQC key in Qpher has an explicit version number. This is one of the most important concepts in the platform.

Why Explicit Versions?

Unlike systems that silently use the "latest" key, Qpher requires you to specify the key_version on every operation. This design choice prevents:

  • Silent key changes -- You always know exactly which key encrypted your data
  • Decryption failures -- The version tells Qpher which key to use for decryption
  • Audit ambiguity -- Every operation is traceable to a specific key version

Version Rules

OperationAllowed Key Statuses
Encryptactive only
Decryptactive or retired
Signactive only
Verifyactive or retired
Always store the key_version

When you encrypt data or sign a message, store the key_version from the response alongside the ciphertext or signature. You will need it for decryption or verification.


Key Lifecycle

PQC keys move through three states during their lifetime:

Active

A newly generated key starts in the active state. Active keys can perform all operations: encrypt, decrypt, sign, and verify. Each tenant can have one active key per algorithm at a time.

Retired

When you rotate a key, the previously active key moves to retired status. Retired keys can still decrypt and verify (backward compatibility), but they cannot encrypt or sign new data. This ensures you can always access data encrypted with older keys.

Archived

When a retired key is no longer needed for any decryption or verification, you can archive it. Archived keys cannot perform any operations. This is a terminal state intended for compliance and record-keeping.

Rotation Flow


Hybrid KEM-DEM Encryption

Qpher does not use Kyber768 alone for encryption. Instead, it implements a hybrid KEM-DEM (Key Encapsulation Mechanism -- Data Encapsulation Mechanism) scheme that combines post-quantum key exchange with proven symmetric encryption.

How It Works

  1. KEM (Kyber768) -- The Kyber768 public key encapsulates a 32-byte shared secret. This step is quantum-resistant.
  2. KDF (HKDF-SHA256) -- The shared secret is fed through HKDF-SHA256 to derive a 256-bit AES key. This adds cryptographic domain separation.
  3. DEM (AES-256-GCM) -- The derived key encrypts your plaintext using AES-256-GCM, which provides both confidentiality and integrity (authenticated encryption).

Ciphertext Structure

The ciphertext returned by the /kem/encrypt endpoint is a single base64-encoded blob containing all four components:

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

You do not need to parse or manage these components. Pass the entire ciphertext value to the /kem/decrypt endpoint along with the correct key_version, and Qpher handles the rest.

Shared secret is ephemeral

The shared secret produced by KEM encapsulation is used only to derive the AES key. It is never stored, logged, or returned in any API response. A fresh shared secret is generated for every encrypt operation.

Why Hybrid?

  • Kyber768 provides quantum resistance -- protecting the key exchange against quantum attacks
  • AES-256-GCM provides efficient authenticated encryption for arbitrary-length data -- Kyber768 alone can only encapsulate a fixed 32-byte secret
  • HKDF-SHA256 provides proper key derivation with domain separation -- preventing key reuse across different contexts

This combination gives you the best of both worlds: quantum-safe key exchange and battle-tested symmetric encryption.


Non-Exportable Private Keys

Qpher follows a strict non-exportable key policy. This is a fundamental security guarantee of the platform.

What This Means

  • Private keys are generated inside the KMS Orchestrator and never leave it
  • No API endpoint exists to retrieve, download, or export private key material
  • The database stores only a private_key_handle (a reference to the encrypted key file), never the key bytes themselves
  • Private keys are encrypted at rest using AES-256-GCM with a Key Encryption Key (KEK)

Why It Matters

If private keys could be exported, any breach of your application could compromise all your encrypted data. By keeping private keys non-exportable:

  • A compromised API key cannot extract private key material
  • Your data remains protected even if your application is breached
  • Key operations (decrypt, sign) require going through Qpher's authenticated API, which enforces rate limits, policy checks, and audit logging
Bring Your Own Key?

Qpher does not currently support importing externally generated private keys. All key generation happens within the platform to ensure the non-exportable guarantee. If you have specific requirements, contact us about Enterprise plans.


Standard Response Format

All Qpher API responses follow a consistent structure:

{
"data": {
// Operation-specific payload
},
"request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"timestamp": "2026-01-15T10:30:00Z"
}
  • data -- Contains the operation result (ciphertext, signature, key details, etc.)
  • request_id -- A unique UUID for tracing and debugging. Include this when contacting support.
  • timestamp -- UTC timestamp of when the response was generated

Next Steps