Cipherion
Sdk IntegrationsPython SDK

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

  1. Exclude unnecessary fields
client =CipherionClient()
result = client.deep_encrypt(
    data,
    DeepEncryptOptions(
        exclude_patterns=["_id", "__v", "*_at", "metadata"]
    )
)
  1. 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
  1. 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 result

Data 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/decryption

null 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)
        raise

3. 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

  1. Production environments - Don't break the application
  2. Data migrations - Some records might be corrupted
  3. Partial data recovery - Get what you can
  4. 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 field

Handling 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 structure

Array 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)
        raise

2. 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 results

3. 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 handling

Common 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 = data

Best 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 = None

Field 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))

On this page