Core Concepts
Install the Cipherion Python SDK
Encryption
Cipherion provides two types of encryption methods: simple string encryption and deep encryption for complex data structures.
Simple Encryption
Use encrypt() to encrypt simple string values:
Synchronous
client = CipherionClient()
def encrypt_string():
try:
plaintext = "Hello, World!"
encrypted = client.encrypt(plaintext)
print(encrypted)
except CipherionError as error:
print("Error:", error.message)
Asynchronous
from cipherion import AsyncCipherionClient
import asyncio
async def main():
client = AsyncCipherionClient()
encrypted = await client.encrypt("Hello, World!")
print(encrypted)
await client.close()
asyncio.run(main())AsyncCipherionClient creates an internal HTTP session for communicating with the Cipherion API. You must call await client.close() when you are done using the client to properly release network resources.
Example: Sensitive Field Encryption
Encrypt any sensitive value such as passwords, credit card numbers, SSNs, API keys, or other confidential fields.
client = CipherionClient()
def encrypt_sensitive_field(value: str) -> dict:
try:
encrypted = client.encrypt(value)
except CipherionError as error:
print("Encryption failed:", error.message)
raise
# Example Usage
result = encrypt_sensitive_field("MY_API_KEY")
print(result)
#{'success': True, 'encryptedValue': encrypted MY_API_KEY}Deep Encryption
Use deep_encrypt() to encrypt complex nested objects and arrays:
def encrypt_object():
client = CipherionClient()
user_data = {
"name": "John Doe",
"email": "john@example.com",
"address": {
"street": "123 Main St",
"city": "New York",
"zipCode": "10001",
},
"phones": ["+1234567890", "+0987654321"],
}
try:
result = client.deep_encrypt(user_data)
print("Encrypted:", result["encrypted"])
print("Metadata:", result["meta"])
except CipherionError as error:
print("Deep encryption failed:", error.message)Response Structure
{
"encrypted": "<encrypted_data>",
"meta": {
"encryptionMetadata": {
"excluded_fields": ["<string>"],
"excluded_patterns": ["<string>"],
"operation": "deep_encrypt"
}
}
}Example with Metadata
client = CipherionClient()
user = {
"_id": "507f1f77bcf86cd799439011",
"username": "johndoe",
"email": "john@example.com",
"ssn": "123-45-6789",
"createdAt": "2024-01-15T10:30:00Z",
}
try:
result = client.deep_encrypt(
user,
DeepEncryptOptions(
exclude_patterns=["_id", "*At"]
)
)
print("Total fields:", result["meta"]["totalFields"])
# Expected: 5
print("Encrypted data:", result["encrypted"])
# {
# "_id": "507f1f77bcf86cd799439011", # Not encrypted
# "username": "encrypted_...",
# "email": "encrypted_...",
# "ssn": "encrypted_...",
# "createdAt": "2024-01-15T10:30:00Z" # Not encrypted
# }
except CipherionError as error:
print("Deep encryption failed:", error.message)Nested Objects
Deep encryption handles nested structures automatically:
def deep_encrypt_example() -> None:
client = CipherionClient()
company = {
"name": "Acme Corp",
"employees": [
{
"id": 1,
"name": "Alice",
"salary": 100000,
"address": {
"street": "456 Oak Ave",
"city": "Boston",
},
},
{
"id": 2,
"name": "Bob",
"salary": 95000,
"address": {
"street": "789 Pine Rd",
"city": "Seattle",
},
},
],
}
try:
result = client.deep_encrypt(
company,
DeepEncryptOptions(
exclude_fields=["employees[0].id", "employees[1].id"], # Keep IDs unencrypted
),
)
print("Encrypted:")
print(result["encrypted"])
# All fields encrypted except employee IDs
except CipherionError as e:
print(f"Error: {e.message}")
deep_encrypt_example()
print("Encrypted data:", result["encrypted"])
#{
#"name": "encrypted_...",
#"employees": [
# {
# "id": 1, # Not encrypted
# "name": "encrypted_...",
# "salary": "encrypted_...",
# "address": {
# "street": "encrypted_...",
# "city": "encrypted_..."
# }
# },
# {
# "id": 2, # Not encrypted
# "name": "encrypted_...",
# "salary": "encrypted_...",
# "address": {
# "street": "encrypted_...",
# "city": "encrypted_..."
# }
# }
# ]
#}Arrays
Arrays are encrypted element by element:
from cipherion import CipherionClient, DeepEncryptOptions
client =CipherionClient()
data = {
"users": ["Alice", "Bob", "Charlie"],
"scores": [95, 87, 92]
}
result = client.deep_encrypt(data)
print(result["encrypted"])
# {
# users: ["encrypted_Alice", "encrypted_Bob", "encrypted_Charlie"],
# scores: ["encrypted_95", "encrypted_87", "encrypted_92"]
# }Selective Array Encryption
Encrypt specific array elements:
client =CipherionClient()
data = {
"users": ["Alice", "Bob", "Charlie", "Diana"]
}
result = client.deep_encrypt(
data,
DeepEncryptOptions(
exclude_fields=["users[1]", "users[3]"]
)
)
print(result["encrypted"])
# {
# users: [
# "encrypted_Alice",
# "Bob", # Not encrypted
# "encrypted_Charlie",
# "Diana" # Not encrypted
# ]
# }Performance Considerations
Optimization Tips
- Exclude unnecessary fields
client =CipherionClient()
result = client.deep_encrypt(
data,
DeepEncryptOptions(
exclude_patterns=["_id", "__v", "*_at", "metadata"]
)
)- Use batch operations for multiple items
# Instead of loop
for item in items:
client.deep_encrypt(item) # ❌ Slow
# Use migration
result = client.migrate_encrypt(items) # ✅ Fast- Cache encrypted data when possible
cache = {}
def get_cached_encryption(key, data, client):
if key in cache:
return cache[key]
result = client.deep_encrypt(data)
cache[key] = result
return resultData Types
Cipherion handles various data types:
mixed = {
"string": "Hello",
"number": 42,
"boolean": True,
"null": None,
"date": datetime.now(UTC).isoformat(),
"array": [1, 2, 3],
"object": {"nested": "value"}
}
result = client.deep_encrypt(mixed)
# All types are preserved after encryption/decryptionnull and undefined values are preserved and not encrypted.
Encryption Best Practices
1. Validate Data Before Encryption
def validate_user_data(data: dict) -> None:
if not data.get("email") or not data.get("name"):
raise ValueError("Missing required fields")
def encrypt_user(user: dict, client: CipherionClient) -> dict:
validate_user_data(user)
return client.deep_encrypt(user)2. Handle Errors Gracefully
import time
from cipherion import CipherionError
def delay(ms: int):
time.sleep(ms / 1000)
def safe_encryption(data, client):
try:
return client.deep_encrypt(data)
except CipherionError as error:
# Python SDK has no isRetryable(), so infer it
if error.status_code >= 500:
# Retry logic
delay(2000)
return client.deep_encrypt(data)
# Equivalent of error.getUserMessage()
print("Encryption failed:", error.message)
raise3. Don't Encrypt Everything
# ❌ Bad: Encrypting IDs and metadata
result = client.deep_encrypt(data)
# ✅ Good: Exclude non-sensitive fields
result = client.deep_encrypt(
data,
DeepEncryptOptions(
exclude_fields=["id", "userId"],
exclude_patterns=["_id", "*_at", "version"]
)
)Decryption
Decrypt data that was encrypted using Cipherion's encryption methods.
Simple Decryption
Use decrypt() to decrypt strings encrypted with encrypt():
Synchronous
from cipherion import CipherionClient
client = CipherionClient()
plaintext = "Hello, World!"
encrypted = client.encrypt(plaintext)
decrypted = client.decrypt(encrypted)
print("Decrypted:", decrypted)
# Output: "Hello, World!"Asynchronous
from cipherion import AsyncCipherionClient
import asyncio
async def decrypt_example():
client = AsyncCipherionClient()
encrypted = await client.encrypt("Secret Data")
decrypted = await client.decrypt(encrypted)
print(decrypted)
await client.close()
asyncio.run(encrypt_object()) Example: Sensitive Field Verification
from cipherion import CipherionClient, CipherionError
client = CipherionClient()
# Verifies a sensitive value by decrypting the stored encrypted value and comparing it with the provided input.
def verify_sensitive_field(input_value: str, encrypted_value: str) -> bool:
try:
decrypted = client.decrypt(encrypted_value)
return input_value == decrypted
except CipherionError as error:
print("Verification failed:", error.message)
return False
# Example Usage
is_valid = verify_sensitive_field("MY_API_KEY", stored_encrypted_value)
print(is_valid)Deep Decryption
Use deep_decrypt() to decrypt complex objects:
def deep_decrypt_example():
try:
user_data = {
"name": "John Doe",
"email": "john@example.com",
"address": {
"street": "123 Main St",
"city": "New York"
}
}
encrypted_result = client.deep_encrypt(user_data)
print("Encrypted:")
print(encrypted_result["encrypted"])
decrypted_result = client.deep_decrypt(encrypted_result["encrypted"])
print("\nDecrypted:")
print(decrypted_result["data"])
except CipherionError as error:
print("Error:", error.message)
Response Structure
response: DeepDecryptResponse = {
"data": any, # decrypted data
"meta": {
"decryptionMetadata": {
"excluded_fields": ["<string>"],
"excluded_patterns": ["<string>"],
"failed_fields": ["<string>"],
"failed_gracefully": ["<bool>"],
"operation": "deep_decrypt",
}
}
}Important: Use Same Exclusions
Always use the same exclude_fields and exclude_patterns during decryption that were used during encryption.
# Encryption
encrypted = client.deep_encrypt(data, DeepEncryptOptions(
exclude_fields =["id"],
exclude_patterns = ["_id", "*_at"])
)
# Decryption - MUST use same options
decrypted = client.deep_decrypt(encrypted["encrypted"], DeepDecryptOptions(
exclude_fields=["id"],
exclude_patterns=["_id", "*_at"])
)Why This Matters
# ❌ Wrong: Different exclusions
encrypted = client.deep_encrypt(data, DeepEncryptOptions(
exclude_patterns = ["_id"])
)
decrypted = client.deep_decrypt(encrypted["encrypted"], DeepDecryptOptions(
exclude_patterns = ["*_id"]) # Different pattern!
)
# Result: May attempt to decrypt unencrypted fields
# ✅ Correct: Same exclusions
encrypted = client.deep_encrypt(data, DeepEncryptOptions(
exclude_patterns = ["_id"])
)
decrypted = client.deep_decrypt(encrypted["encrypted"], DeepDecryptOptions(
exclude_patterns = ["_id"]) # Same pattern
)Graceful Failure Handling
Handle corrupted or invalid encrypted data gracefully:
def main():
try:
original_data = {
"_id": "123",
"name": "John Doe",
"email": "john@example.com",
"phone": "+1234567890"
}
encrypted = client.deep_encrypt(
original_data,
DeepEncryptOptions(exclude_patterns=["_id"])
)
encrypted_data = encrypted["encrypted"]
# Corrupt AFTER encryption
encrypted_data["name"] = "corrupted_data"
result = client.deep_decrypt(
encrypted_data,
DeepDecryptOptions(
exclude_patterns=["_id"],
fail_gracefully=True
)
)
print("Decrypted data:", result["data"])
failed_fields = (
result
.get("meta", {})
.get("decryptedMetadata", {})
.get("failed_fields", [])
)
print("Failed fields:", failed_fields)
# "name"When to Use Graceful Failure
- Production environments - Don't break the application
- Data migrations - Some records might be corrupted
- Partial data recovery - Get what you can
- Logging and monitoring - Track decryption issues
Without Graceful Failure
try:
result = client.deep_decrypt(encrypted["encrypted"], DeepDecryptOptions(
"fail_gracefully": False) # Throw on first failure (default)
)
except Exception as error:
print('Decryption failed:', error)
# Entire operation fails on first corrupted fieldHandling Failed Fields
def decrypt_with_recovery(encrypted: dict):
try:
result = client.deep_decrypt(
encrypted,
DeepDecryptOptions(
fail_gracefully=True
)
)
decrypted_data = result.get("data", {})
#Check for failures
failed_fields = (
result
.get("meta", {})
.get("decryptedMetadata", {})
.get("failed_fields", [])
)
if failed_fields:
print("Failed fields:", failed_fields)
return decrypted_data
except CipherionError as error:
print("Decryption failed completely:", error.message)
raise
#Usage:
if __name__ == "__main__":
sample_data = {
"_id": "123",
"name": "John Doe",
"email": "john@example.com"
}
encrypted = client.deep_encrypt(sample_data)
encrypted_data = encrypted["encrypted"]
# simulate corruption
encrypted_data["name"] = "corrupted_data"
decrypted = decrypt_with_recovery(encrypted_data)
print("Final result:", decrypted)Nested Objects Decryption
data = {
"user": {
"profile": {
"name": "John Doe",
"email": "john@example.com"
},
"settings": {
"theme": "dark",
"notifications": "enabled"
}
}
}
encrypted = client.deep_encrypt(data)
encrypted_data = encrypted["encrypted"]
result = client.deep_decrypt(encrypted_data)
print(result["data"])
# Fully decrypted nested structureArray Decryption
data = {
"users": ["Alice", "Bob", "Charlie"],
"scores": [95, 87, 92]
}
encrypted = client.deep_encrypt(data)
encrypted_data = encrypted["encrypted"]
result = client.deep_decrypt(encrypted_data)
decrypted_data = result["data"]
print(decrypted_data)
#{'users': ['Alice', 'Bob', 'Charlie'], 'scores': ['95', '87', '92']}Selective Array Decryption
# If you excluded specific array elements during encryption
encrypted = {
"users": [
"encrypted_Alice",
"Bob", # Was excluded during encryption
"encrypted_Charlie",
"Diana" # Was excluded during encryption
]
}
result = client.deep_decrypt(
encrypted_data,
DeepDecryptOptions(
exclude_fields=["users[1]", "users[3]"])
)
print(result["data"]["users"])
# ["Alice", "Bob", "Charlie", "Diana"]Storing Decryption Options
Store encryption options with your data for consistent decryption:
# During encryption
encryption_options = {
"exclude_fields": ["id", "userId"],
"exclude_patterns": ["_id", "*_at"]
}
encrypted = client.deep_encrypt(
data,
DeepEncryptOptions(**encryption_options)
)
# Store both encrypted data and options
database["record_1"] = {
"data": encrypted["encrypted"],
"_encryptionOptions": encryption_options,
"_encrypted": True
}
record = database["record_1"]
if record.get("_encrypted") is True:
decrypted = client.deep_decrypt(
record["data"],
DeepDecryptOptions(**record["_encryptionOptions"])
)
print("Decrypted record:")
print(decrypted["data"])
else:
print("Record is not encrypted.")Performance Optimization
1. Cache Decrypted Data
from cachetools import TTLCache
decryption_cache = TTLCache(
maxsize=500,
ttl=300 # 5 minutes (seconds)
)
def cached_decrypt(encrypted: dict, cache_key: str):
#Check cache first
cached = decryption_cache.get(cache_key)
if cached:
return cached
try:
result = client.deep_decrypt(
encrypted,
DeepDecryptOptions(
exclude_patterns=["_id"],
fail_gracefully=True
)
)
decrypted_data = result["data"]
# Store in cache
decryption_cache[cache_key] = decrypted_data
return decrypted_data
except CipherionError as error:
print("Decryption failed:", error.message)
raise2. Parallel Decryption
from concurrent.futures import ThreadPoolExecutor, as_completed
def decrypt_single(item):
result = client.deep_decrypt(
item,
DeepDecryptOptions(
exclude_patterns=["_id"],
fail_gracefully=True
)
)
return result["data"]
def decrypt_multiple(encrypted_array: list):
results = []
# Use thread pool for parallel I/O calls
with ThreadPoolExecutor(max_workers=5) as executor:
futures = [executor.submit(decrypt_single, item) for item in encrypted_array]
for future in as_completed(futures):
results.append(future.result())
return results3. Lazy Decryption
Only decrypt fields when needed:
class LazyDecryptedUser:
def __init__(self, encrypted: dict):
self._decrypted = None
self._encrypted = encrypted
def _decrypt_if_needed(self):
if self._decrypted is None:
result = client.deep_decrypt(self._encrypted)
self._decrypted = result["data"]
def get_name(self):
self._decrypt_if_needed()
return self._decrypted.get("name")
def get_email(self):
self._decrypt_if_needed()
return self._decrypted.get("email")Error Handling
def safe_decryption(encrypted):
try:
return client.deep_decrypt(
encrypted,
DeepDecryptOptions(fail_gracefully=False)
)
except CipherionError as error:
if error.status_code == 400:
print("Invalid encrypted data format")
elif error.status_code == 401:
print("Invalid credentials")
elif error.status_code == 500:
print("Server error during decryption")
if error.is_retryable():
# Retry logic
return client.deep_decrypt(
encrypted,
DeepDecryptOptions(fail_gracefully=False)
)
else:
print("Decryption error:", error.get_user_message())
raise # re-raise the exception after handlingCommon Pitfalls
1. Mismatched Exclusions
# ❌ Wrong
encrypted = client.deep_encrypt(data, exclude_patterns=["_id", "*_at"])
decrypted = client.deep_decrypt(encrypted["encrypted"],exclude_patterns=["_id"] # Missing "*_at")
# ✅ Correct
options = {
"exclude_patterns": ["id", "*_at"]
}
encrypted = client.deep_encrypt(data,DeepEncryptOptions(**options))
decrypted = client.deep_decrypt(encrypted["encrypted"],DeepDecryptOptions(**options))2. Not Handling Failures
# ❌ Wrong - assumes all fields decrypt successfully
result = client.deep_decrypt(encrypted)
email = result["data"]["email"] # Might be corrupted!
# ✅ Correct - check for failures
encrypted = client.deep_encrypt( {"email": "john.doe@example.com", "name": "John Doe"})
decrypted = client.deep_decrypt(
encrypted["encrypted"],
DeepDecryptOptions(fail_gracefully=True)
)
failed_fields = (
decrypted
.get("meta", {})
.get("decryptedMetadata", {})
.get("failed_fields", [])
)
if "email" in failed_fields:
print("Warning: Email field is corrupted")
# Handle appropriately
else:
email = decrypted["data"]["email"]3. Decrypting Unencrypted Data
# ❌ Wrong - no check
decrypted = client.deep_decrypt(data)
# ✅ Correct - verify encryption flag
if "encrypted" in data:
decrypted = client.deep_decrypt(data)
else:
# Data is already in plaintext
decrypted = dataBest Practices
1. Store encryption options with data
database.save({
"data": encrypted["encrypted"],
"_encryptionOptions": asdict(options) if options else None
})2. Use graceful failure in production
is_production = os.getenv("NODE_ENV") == "production"
decrypted = client.deep_decrypt(
encrypted,
DeepDecryptOptions(fail_gracefully=is_production)
)3. Monitor failed decryptions
failed_fields = (
decrypted
.get("meta", {})
.get("decryptedMetadata", {})
.get("failed_fields", [])
)
if len(failed_fields) > 0:
metrics.increment(
"decryption_failures",
{"fields": failed_fields}
)4. Clear decrypted data after use
decrypted = client.deep_decrypt(encrypted)
# Use the data
process_data(decrypted["data"])
# Clear from memory
decrypted = NoneField Exclusions
Field exclusions allow you to selectively encrypt data while keeping certain fields in plaintext. This is crucial for maintaining searchability, reducing costs, and preserving data structure.
Why Exclude Fields?
1. Maintain Searchability
Database queries require unencrypted fields:
encrypted = client.deep_encrypt(
user,
DeepEncryptOptions(
exclude_fields=["userId"])
)
# Now you can query:
# db.users.find({"userId": "123"})2. Reduce Costs
Only encrypt sensitive data:
encrypted = client.deep_encrypt(
sensitive_data,
DeepEncryptOptions(exclude_patterns=["_id", "__v", "*_at"])
)
3. Preserve Metadata
Keep system fields unencrypted:
encrypted = client.deep_encrypt(
document,
DeepEncryptOptions(exclude_patterns=["createdAt", "updatedAt", "version"])
)4. Improve Performance
Fewer encrypted fields = faster operations:
encrypted = client.deep_encrypt(
data,
DeepEncryptOptions(exclude_patterns=["_id", "metadata.*", "stats.*"])
)Exclusion Methods
1. Exact Path Matching (exclude_fields)
Specify exact paths to exclude:
data = {
"id": "user_123",
"profile": {
"name": "John Doe",
"email": "john@example.com"
},
"settings": {
"theme": "dark",
"language": "en"
}
}
encrypted = client.deep_encrypt(
data,
DeepEncryptOptions(exclude_fields=[
"id", # Top-level
"profile.email", # Nested
"settings.theme" # Nested
])
)2. Pattern Matching (exclude_patterns)
Use patterns for flexible matching:
encrypted = client.deep_encrypt(
data,
DeepEncryptOptions(exclude_patterns=[
"_id", # Exact name
"__v", # Exact name
"*_at", # Ends with _at
"timestamp*", # Starts with timestamp
"meta_*" # Starts with meta_
])
)Common Patterns
MongoDB Documents
mongo_options = {
"exclude_patterns": ["_id", "__v", "*At"]
}
encrypted = client.deep_encrypt(document, DeepEncryptOptions(**mongo_options))User Profiles
user_options = {
"exclude_fields": ["id", "username", "role"],
"exclude_patterns": ["*_at", "lastLogin"]
}
encrypted = client.deep_encrypt(user, DeepEncryptOptions(**user_options))E-commerce Orders
order_options = {
"exclude_fields": [
"orderId",
"customerId",
"items[*].productId"
],
"exclude_patterns": ["_id", "*_at", "status", "total"]
}
encrypted = client.deep_encrypt(order, DeepEncryptOptions(**order_options))