Skip to main content

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)
Reduced Security — Use With Caution

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 CaseWhy Deterministic Helps
Audit trailsVerify that a stored ciphertext matches expected plaintext without decrypting
Compliance checksProve data integrity by reproducing the same ciphertext from known inputs
DeduplicationDetect duplicate encrypted records without decrypting them
Integrity verificationConfirm that encrypted data has not been modified
When NOT to Use

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()
Salt Best Practices

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

POST/api/v1/kem/encryptEncrypt with deterministic mode (same plaintext + salt = same ciphertext)
Deterministic Encryption
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

RequestPOST/api/v1/kem/encrypt
Content-Type: application/json
x-api-key: qph_your_key_here
{
  "plaintext": "SGVsbG8sIFdvcmxkIQ==",
  "key_version": 1,
  "mode": "deterministic",
  "salt": "c2FsdC1mb3ItaW52b2ljZS0yMDI2LTAwMQ=="
}
Response200
{
  "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

PropertyStandard (default)Deterministic
Same input produces same outputNo (randomized)Yes (with same salt)
Security levelMaximumReduced (leaks equality)
Requires saltNoYes
Default modeYesNo (opt-in only)
Use caseGeneral encryptionAudit, compliance, dedup

Error Handling

HTTP StatusError CodeCauseResolution
400ERR_INVALID_001mode is "deterministic" but salt is missingProvide a base64-encoded salt
400ERR_INVALID_002salt is not valid base64Ensure salt is properly base64-encoded
400ERR_KEM_007Invalid mode valueUse "standard" or "deterministic"
401ERR_AUTH_001Invalid or missing API keyCheck your x-api-key header