Deterministic Encryption
This guide covers Qpher opt-in deterministic encryption mode, which produces the same ciphertext for the same plaintext and salt — useful for audit trails, compliance checks, and data deduplication.
Prerequisites
- A Qpher account with an active API key
- At least one active Kyber768 key pair (see Key Management)
- Understanding of when deterministic encryption is appropriate (see When to Use below)
Deterministic encryption produces identical ciphertext for identical plaintext and salt. This means an attacker can detect when the same data is encrypted twice. Only use this mode when the specific use case requires it, and never as the default encryption mode.
How It Works
In standard (default) mode, Qpher encryption is fully randomized — encrypting the same plaintext twice produces different ciphertexts. This is the most secure mode and should be your default.
In deterministic mode, a user-provided salt is used as input to the key derivation step, making the output reproducible:
Standard mode: plaintext + random nonce → unique ciphertext (every time)
Deterministic: plaintext + salt + key_version → same ciphertext (every time)
Deterministic mode is opt-in only. You must explicitly set mode: "deterministic" and provide a salt in the request.
When to Use
| Use Case | Why Deterministic Helps |
|---|---|
| Audit trails | Verify that a stored ciphertext matches expected plaintext without decrypting |
| Compliance checks | Prove data integrity by reproducing the same ciphertext from known inputs |
| Deduplication | Detect duplicate encrypted records without decrypting them |
| Integrity verification | Confirm that encrypted data has not been modified |
Do not use deterministic encryption for general-purpose data protection. If an attacker can guess possible plaintexts (e.g., "yes" or "no", Social Security numbers, credit card numbers), they can compare ciphertexts to confirm their guesses. Always use standard mode for sensitive data encryption.
Step 1: Choose a Salt
The salt must be base64-encoded and should be unique to the context of your operation. Good salt choices include:
- A document ID or record identifier
- A combination of tenant ID and record type
- A stable hash of metadata associated with the plaintext
import base64
import hashlib
# Example: derive a salt from a document ID
doc_id = "invoice-2026-001"
salt = base64.b64encode(
hashlib.sha256(doc_id.encode()).digest()
).decode()
Use a different salt for each logical record or document. Reusing the same salt across different records means identical plaintexts in those records will produce identical ciphertexts, which may leak information about data patterns.
Step 2: Send the Deterministic Encrypt Request
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,
"mode": "deterministic",
"salt": "c2FsdC1mb3ItaW52b2ljZS0yMDI2LTAwMQ=="
}'Step 3: Understand the Response
/api/v1/kem/encryptContent-Type: application/json
x-api-key: qph_your_key_here{
"plaintext": "SGVsbG8sIFdvcmxkIQ==",
"key_version": 1,
"mode": "deterministic",
"salt": "c2FsdC1mb3ItaW52b2ljZS0yMDI2LTAwMQ=="
}{
"data": {
"ciphertext": "base64-encoded-deterministic-ciphertext...",
"key_version": 1,
"algorithm": "Kyber768",
"mode": "deterministic"
},
"request_id": "990e8400-e29b-41d4-a716-446655440000",
"timestamp": "2026-02-15T10:35:00Z"
}The response is identical to standard encryption, with the addition of "mode": "deterministic" to confirm the mode used.
Decrypting Deterministic Ciphertext
Decryption works exactly the same as standard mode. You do not need to provide the salt or mode during decryption:
response = requests.post(
"https://api.qpher.ai/api/v1/kem/decrypt",
headers={
"Content-Type": "application/json",
"x-api-key": "qph_your_key_here",
},
json={
"ciphertext": deterministic_ciphertext,
"key_version": 1,
},
)
Comparison: Standard vs. Deterministic
| Property | Standard (default) | Deterministic |
|---|---|---|
| Same input produces same output | No (randomized) | Yes (with same salt) |
| Security level | Maximum | Reduced (leaks equality) |
| Requires salt | No | Yes |
| Default mode | Yes | No (opt-in only) |
| Use case | General encryption | Audit, compliance, dedup |
Error Handling
| HTTP Status | Error Code | Cause | Resolution |
|---|---|---|---|
| 400 | ERR_INVALID_001 | mode is "deterministic" but salt is missing | Provide a base64-encoded salt |
| 400 | ERR_INVALID_002 | salt is not valid base64 | Ensure salt is properly base64-encoded |
| 400 | ERR_KEM_007 | Invalid mode value | Use "standard" or "deterministic" |
| 401 | ERR_AUTH_001 | Invalid or missing API key | Check your x-api-key header |
Related Guides
- Encrypt Data — Standard (randomized) encryption guide
- Decrypt Data — Decrypt deterministic or standard ciphertext
- Key Versioning — How key versions interact with deterministic mode
- Key Management — Manage your encryption keys