Production-grade, framework-agnostic Python cryptographic utility package
Project description
Cryptographic Utilities for ECL microservices
Production-grade, framework-agnostic Python cryptographic utility package.
Table of Contents
- Installation
- Quick Start
- Auto-Configuration
- Symmetric Encryption
- Asymmetric Operations
- Hashing
- Key Derivation
- Token Generation
- Envelope Encryption
- KMS Backends
- DEK Caching
- Audit Logging
- Exceptions
- Changelog
- Security Policy
- License
Installation
# Core package
pip install ecl_crypto_utility
# With AWS KMS support
pip install "ecl_crypto_utility[aws]"
# With GCP Cloud KMS support
pip install "ecl_crypto_utility[gcp]"
# With HashiCorp Vault support
pip install "ecl_crypto_utility[vault]"
# All KMS backends
pip install "ecl_crypto_utility[all]"
# Development dependencies
pip install "ecl_crypto_utility[dev]"
Quick Start
Set two environment variables and you're ready:
export ECL_CRYPTO_KMS_BACKEND=local # or: aws, gcp, vault
export ECL_CRYPTO_KMS_KEY_ID=my-app-key
from ecl_crypto_utility import encrypt, decrypt
ciphertext = encrypt(plaintext=b"Hello, World!")
plaintext = decrypt(encrypted_data=ciphertext)
assert plaintext == b"Hello, World!"
The package reads ENV vars on first call, auto-configures the KMS backend and DEK cache, and caches the singleton for all subsequent calls. No manual backend or encryptor setup needed.
For direct symmetric encryption (you manage the key yourself):
from ecl_crypto_utility.symmetric import generate_key, encrypt, decrypt
key = generate_key()
ciphertext = encrypt(plaintext=b"Hello, World!", key=key)
plaintext = decrypt(encrypted_data=ciphertext, key=key)
Auto-Configuration
The top-level encrypt() and decrypt() use envelope encryption under the hood, configured entirely via environment variables.
Required Variables
| Variable | Description |
|---|---|
ECL_CRYPTO_KMS_BACKEND |
Backend to use: aws, gcp, vault, or local |
ECL_CRYPTO_KMS_KEY_ID |
KMS key identifier (ARN, alias, key name, etc.) |
AWS Backend Variables
All optional — uses the standard boto3 credential chain by default.
| Variable | Description |
|---|---|
ECL_CRYPTO_AWS_REGION |
AWS region (e.g., us-east-1) |
ECL_CRYPTO_AWS_PROFILE |
AWS credential profile name |
ECL_CRYPTO_AWS_ENDPOINT_URL |
Custom endpoint (e.g., http://localhost:4566 for LocalStack) |
GCP Backend Variables
| Variable | Required | Description |
|---|---|---|
ECL_CRYPTO_GCP_PROJECT_ID |
Yes | GCP project ID |
ECL_CRYPTO_GCP_LOCATION_ID |
Yes | Cloud KMS location (e.g., us-east1) |
ECL_CRYPTO_GCP_KEY_RING_ID |
Yes | Key ring name |
Vault Backend Variables
| Variable | Required | Description |
|---|---|---|
ECL_CRYPTO_VAULT_URL |
Yes | Vault server URL |
ECL_CRYPTO_VAULT_TOKEN |
No | Auth token (falls back to env-based auth) |
ECL_CRYPTO_VAULT_MOUNT_POINT |
No | Transit engine mount (default: transit) |
ECL_CRYPTO_VAULT_NAMESPACE |
No | Vault namespace (enterprise) |
Local Backend Variables
| Variable | Required | Description |
|---|---|---|
ECL_CRYPTO_LOCAL_MASTER_KEY |
No | Hex-encoded 32-byte master key. Auto-generated if absent. |
Cache Variables
| Variable | Default | Description |
|---|---|---|
ECL_CRYPTO_DEK_CACHE_ENABLED |
true |
Enable/disable DEK caching |
ECL_CRYPTO_DEK_CACHE_MAX_SIZE |
100 |
Max cached entries |
ECL_CRYPTO_DEK_CACHE_TTL_SECONDS |
300 |
TTL per entry (seconds) |
Example .env Files
AWS:
ECL_CRYPTO_KMS_BACKEND=aws
ECL_CRYPTO_KMS_KEY_ID=arn:aws:kms:us-east-1:123456789012:key/abcd-1234
ECL_CRYPTO_AWS_REGION=us-east-1
GCP:
ECL_CRYPTO_KMS_BACKEND=gcp
ECL_CRYPTO_KMS_KEY_ID=my-crypto-key
ECL_CRYPTO_GCP_PROJECT_ID=my-project
ECL_CRYPTO_GCP_LOCATION_ID=us-east1
ECL_CRYPTO_GCP_KEY_RING_ID=my-keyring
Vault:
ECL_CRYPTO_KMS_BACKEND=vault
ECL_CRYPTO_KMS_KEY_ID=my-transit-key
ECL_CRYPTO_VAULT_URL=https://vault.example.com:8200
ECL_CRYPTO_VAULT_TOKEN=hvs.my-vault-token
Local (development):
ECL_CRYPTO_KMS_BACKEND=local
ECL_CRYPTO_KMS_KEY_ID=dev-key
Symmetric Encryption
AES-256-GCM authenticated encryption. A 12-byte nonce is generated internally for every encryption call -- never accepted as input, eliminating nonce reuse. The output is a single Base64-encoded string containing the nonce, ciphertext, and authentication tag.
API
| Function | Signature | Returns |
|---|---|---|
generate_key |
generate_key() -> bytes |
32-byte AES-256 key |
encrypt |
encrypt(plaintext: bytes, key: bytes, associated_data: bytes | None = None) -> str |
Base64 string |
decrypt |
decrypt(encrypted_data: str, key: bytes, associated_data: bytes | None = None) -> bytes |
Plaintext bytes |
Usage
from ecl_crypto_utility import generate_key, encrypt, decrypt
key = generate_key()
ciphertext = encrypt(plaintext=b"hello, world", key=key)
plaintext = decrypt(encrypted_data=ciphertext, key=key)
With Associated Data (AAD) -- authenticated but not encrypted context binding:
context = b"user_id=42"
ciphertext = encrypt(plaintext=b"payment details", key=key, associated_data=context)
plaintext = decrypt(encrypted_data=ciphertext, key=key, associated_data=context)
Decrypting with a different or missing associated_data raises DecryptionError.
Wire Format
nonce (12 bytes) || ciphertext (variable) || authentication tag (16 bytes)
Exceptions
| Exception | When |
|---|---|
InvalidParameterError |
Key is not exactly 32 bytes |
EncryptionError |
AES-256-GCM encryption fails |
DecryptionError |
Wrong key, corrupted data, or AAD mismatch |
Security Notes
- Nonce generated via
os.urandom()for every call -- no caller-supplied nonces. - Only 256-bit (32-byte) keys accepted.
- AES-GCM 128-bit auth tag detects any tampering.
- AAD is authenticated but transmitted in the clear -- do not place secrets in it.
Asymmetric Operations
RSA-4096-OAEP encryption and ECDSA SECP384R1 digital signatures.
RSA API
| Function | Signature | Returns |
|---|---|---|
generate_rsa_key_pair |
generate_rsa_key_pair() -> tuple[bytes, bytes] |
(private_pem, public_pem) |
rsa_encrypt |
rsa_encrypt(plaintext: bytes, public_key_pem: bytes) -> str |
Base64 string |
rsa_decrypt |
rsa_decrypt(encrypted_data: str, private_key_pem: bytes) -> bytes |
Plaintext bytes |
ECDSA API
| Function | Signature | Returns |
|---|---|---|
generate_ecdsa_key_pair |
generate_ecdsa_key_pair() -> tuple[bytes, bytes] |
(private_pem, public_pem) |
sign |
sign(data: bytes, private_key_pem: bytes) -> str |
Base64 signature |
verify |
verify(data: bytes, signature: str, public_key_pem: bytes) -> bool |
True; raises on failure |
Usage
from ecl_crypto_utility import generate_rsa_key_pair, rsa_encrypt, rsa_decrypt
private_key, public_key = generate_rsa_key_pair()
ciphertext = rsa_encrypt(plaintext=b"secret message", public_key_pem=public_key)
plaintext = rsa_decrypt(encrypted_data=ciphertext, private_key_pem=private_key)
from ecl_crypto_utility import generate_ecdsa_key_pair, sign, verify
private_key, public_key = generate_ecdsa_key_pair()
signature = sign(data=b"important document", private_key_pem=private_key)
verify(data=b"important document", signature=signature, public_key_pem=public_key)
Algorithm Details
| Parameter | RSA | ECDSA |
|---|---|---|
| Key size | 4096 bits | SECP384R1 (P-384) |
| Hash | SHA-256 | SHA-384 |
| Padding/Scheme | OAEP (MGF1-SHA-256) | ECDSA |
| Max plaintext | ~446 bytes | N/A (signing only) |
| Key format | PEM (PKCS8 / SubjectPublicKeyInfo) | PEM (PKCS8 / SubjectPublicKeyInfo) |
Exceptions
| Exception | When |
|---|---|
KeyGenerationError |
Key pair generation fails |
EncryptionError |
RSA-OAEP encryption fails (e.g., plaintext too large) |
DecryptionError |
RSA-OAEP decryption fails (wrong key, corrupted data) |
SigningError |
ECDSA signing fails |
VerificationError |
Signature is invalid |
Security Notes
verify()raisesVerificationErroron invalid signatures -- it never returnsFalse.- RSA-OAEP is probabilistic (same plaintext produces different ciphertexts).
- Private keys are serialized without encryption -- protect them at rest.
- RSA key pairs are for encryption; ECDSA key pairs are for signing. Do not interchange them.
Hashing
Three categories of hashing for different purposes:
- Password hashing (Argon2id) -- memory-hard, for storing user passwords
- Integrity hashing (SHA-256) -- fast, for verifying data has not been tampered with
- HMAC (HMAC-SHA256) -- keyed hash for authenticating data
API
| Function | Signature | Returns |
|---|---|---|
hash_password |
hash_password(password: str) -> str |
Argon2-encoded string |
verify_password |
verify_password(password: str, hash_string: str) -> bool |
True; raises on mismatch |
hash_data |
hash_data(data: bytes) -> str |
Hex SHA-256 digest |
verify_data_integrity |
verify_data_integrity(data: bytes, expected_hash: str) -> bool |
True; raises on mismatch |
hmac_sign |
hmac_sign(data: bytes, key: bytes) -> str |
Hex HMAC-SHA256 |
hmac_verify |
hmac_verify(data: bytes, key: bytes, expected_hmac: str) -> bool |
True; raises on mismatch |
Usage
from ecl_crypto_utility import hash_password, verify_password
from ecl_crypto_utility.exceptions import HashingError
hashed = hash_password(password="my_secure_password")
try:
verify_password(password="my_secure_password", hash_string=hashed)
print("Password matches")
except HashingError:
print("Password does not match")
from ecl_crypto_utility import hash_data, verify_data_integrity
digest = hash_data(data=b"important document")
verify_data_integrity(data=b"important document", expected_hash=digest)
from ecl_crypto_utility import generate_key, hmac_sign, hmac_verify
key = generate_key()
mac = hmac_sign(data=b"api request payload", key=key)
hmac_verify(data=b"api request payload", key=key, expected_hmac=mac)
Argon2id Parameters
| Parameter | Value |
|---|---|
| Algorithm | Argon2id |
| Time cost | 3 iterations |
| Memory cost | 65536 KB (64 MB) |
| Parallelism | 4 threads |
| Hash length | 32 bytes |
Security Notes
- All verify functions raise
HashingErroron mismatch -- they never returnFalse. verify_data_integrity()andhmac_verify()usehmac.compare_digest()for constant-time comparison (prevents timing attacks).- Argon2id is intentionally slow (64 MB memory, 3 iterations) -- do not use for high-throughput integrity checks.
- SHA-256 is not suitable for passwords. Never use
hash_data()for password storage. - HMAC requires a secret key. If the key leaks, an attacker can forge valid HMACs.
- Salt is embedded in the Argon2 output -- no separate salt management needed.
Key Derivation
HKDF-SHA256 (from key material) and PBKDF2-HMAC-SHA256 (from passphrases).
API
| Function | Signature | Returns |
|---|---|---|
derive_key_hkdf |
derive_key_hkdf(input_key_material: bytes, length: int = 32, salt: bytes | None = None, info: bytes | None = None) -> bytes |
Derived key bytes |
derive_key_pbkdf2 |
derive_key_pbkdf2(passphrase: str, length: int = 32, salt: bytes | None = None, iterations: int = 600000) -> tuple[bytes, bytes] |
(derived_key, salt) |
Usage
from ecl_crypto_utility import generate_key, derive_key_hkdf
master_secret = generate_key()
derived = derive_key_hkdf(
input_key_material=master_secret,
length=32,
salt=b"static-application-salt",
info=b"encryption-key-v1",
)
from ecl_crypto_utility import derive_key_pbkdf2
derived_key, salt = derive_key_pbkdf2(passphrase="user-chosen-password")
# IMPORTANT: Store the salt alongside the derived key.
# To re-derive later:
rederived_key, _ = derive_key_pbkdf2(passphrase="user-chosen-password", salt=salt)
assert rederived_key == derived_key
When to Use Which
| Scenario | Function |
|---|---|
| Deriving from a Diffie-Hellman shared secret | derive_key_hkdf |
Deriving multiple keys from one master secret (different info values) |
derive_key_hkdf |
| Deriving from a user-entered passphrase | derive_key_pbkdf2 |
Algorithm Details
| HKDF | PBKDF2 | |
|---|---|---|
| Algorithm | HKDF-SHA256 | PBKDF2-HMAC-SHA256 |
| Default output | 32 bytes | 32 bytes |
| Default salt | Random 32 bytes | Random 32 bytes |
| Default iterations | N/A | 600,000 |
Security Notes
- PBKDF2 salt management: The returned salt must be stored alongside the derived key. Without it, the key cannot be re-derived.
- HKDF with
salt=None: Random salt is generated internally and cannot be retrieved. Only use this when the derived key will be stored, not re-derived. - HKDF is not for passwords: It is fast and not memory-hard. For passwords, use
derive_key_pbkdf2orhash_password. - Iteration count: 600,000 follows OWASP recommendations. Increase for stronger brute-force resistance.
Token Generation
Cryptographically secure random tokens for API keys, session IDs, CSRF tokens, password reset tokens, etc.
API
| Function | Signature | Returns |
|---|---|---|
generate_token_bytes |
generate_token_bytes(length: int = 32) -> bytes |
Raw bytes |
generate_token_hex |
generate_token_hex(length: int = 32) -> str |
Hex string (length * 2 chars) |
generate_token_urlsafe |
generate_token_urlsafe(length: int = 32) -> str |
URL-safe Base64 string |
Usage
from ecl_crypto_utility import generate_token_hex, generate_token_urlsafe
api_key = generate_token_hex(length=32) # 64-char hex string
reset_token = generate_token_urlsafe(length=32) # ~43-char URL-safe string
Output Format
| Function | Input length=32 |
Output Length | Character Set |
|---|---|---|---|
generate_token_bytes |
32 bytes | 32 bytes | Raw (0x00-0xFF) |
generate_token_hex |
32 bytes | 64 characters | 0-9, a-f |
generate_token_urlsafe |
32 bytes | ~43 characters | A-Z, a-z, 0-9, -, _ |
Security Notes
- Uses
os.urandom()/secrets-- cryptographically secure. - Default 32 bytes (256 bits) provides strong brute-force resistance. Do not reduce below 16 bytes.
- Tokens are random values, not ciphertexts. Treat them as secrets.
Envelope Encryption
Two-layer encryption: data is encrypted locally with a random DEK (AES-256-GCM), and the DEK is wrapped by a KMS backend. The result is a single opaque Base64-encoded string.
API
class EnvelopeEncryptor(kms_backend: KMSBackend, key_id: str, dek_cache: DEKCache | None = None)
| Method | Signature | Returns |
|---|---|---|
encrypt |
encrypt(plaintext: bytes) -> str |
Base64 envelope string |
decrypt |
decrypt(envelope_data: str) -> bytes |
Plaintext bytes |
Usage
from ecl_crypto_utility import EnvelopeEncryptor, LocalKMSBackend
backend = LocalKMSBackend()
encryptor = EnvelopeEncryptor(kms_backend=backend, key_id="dev-key")
envelope = encryptor.encrypt(plaintext=b"sensitive customer data")
plaintext = encryptor.decrypt(envelope_data=envelope)
With AWS KMS:
from ecl_crypto_utility import EnvelopeEncryptor
from ecl_crypto_utility.kms.aws import AWSKMSBackend
backend = AWSKMSBackend(region_name="us-east-1")
encryptor = EnvelopeEncryptor(
kms_backend=backend,
key_id="arn:aws:kms:us-east-1:123456789012:key/abcd-1234",
)
envelope = encryptor.encrypt(plaintext=b"production data")
With DEK Caching:
from ecl_crypto_utility import DEKCache, EnvelopeEncryptor, LocalKMSBackend
cache = DEKCache(max_size=100, ttl_seconds=300)
encryptor = EnvelopeEncryptor(kms_backend=LocalKMSBackend(), key_id="dev-key", dek_cache=cache)
envelope = encryptor.encrypt(plaintext=b"data")
result = encryptor.decrypt(envelope_data=envelope) # KMS call, DEK cached
result = encryptor.decrypt(envelope_data=envelope) # Cache hit, no KMS call
Encryption Flow
- Generate a new 256-bit DEK.
- Encrypt the plaintext with the DEK (AES-256-GCM).
- Wrap the DEK via the KMS backend.
- Build a JSON envelope and Base64-encode it.
- Emit an audit log entry.
Decryption Flow
- Base64-decode and parse the envelope JSON.
- Validate the envelope version.
- Check the DEK cache (if provided).
- On cache miss, unwrap the DEK via the KMS backend.
- Cache the DEK (if cache provided).
- Decrypt the data with the DEK.
- Emit an audit log entry.
Envelope Format
The Base64 string decodes to JSON:
{
"version": 1,
"encrypted_dek": "<base64>",
"key_id": "<KMS key identifier>",
"encrypted_data": "<base64 AES-256-GCM ciphertext>",
"algorithm": "AES-256-GCM",
"timestamp": "<ISO 8601 UTC>"
}
Security Notes
- Each
encrypt()generates a unique DEK -- no reuse. - Plaintext DEK never leaves memory during encryption.
- Envelope version is validated during decryption.
- All KMS operations are audit-logged.
LocalKMSBackendis for development only.
KMS Backends
All backends implement the KMSBackend abstract interface:
| Method | Returns | Description |
|---|---|---|
encrypt_key(key_material, key_id) |
Base64 string | Wrap a DEK |
decrypt_key(encrypted_key, key_id) |
Plaintext bytes | Unwrap a DEK |
generate_data_key(key_id) |
(plaintext_key, encrypted_key_base64) | Generate and wrap a new DEK |
LocalKMSBackend (Development Only)
from ecl_crypto_utility import LocalKMSBackend
backend = LocalKMSBackend() # Auto-generated master key
backend = LocalKMSBackend(master_key=my_32_byte_key) # Explicit key
| Parameter | Type | Default | Description |
|---|---|---|---|
master_key |
bytes | None |
None |
32-byte key. Random if None. |
Emits WARNING logs on every operation.
AWSKMSBackend
pip install "ecl_crypto_utility[aws]"
from ecl_crypto_utility.kms.aws import AWSKMSBackend
backend = AWSKMSBackend(region_name="us-east-1")
backend = AWSKMSBackend(region_name="us-east-1", endpoint_url="http://localhost:4566") # LocalStack
| Parameter | Type | Default | Description |
|---|---|---|---|
region_name |
str | None |
None |
AWS region |
profile_name |
str | None |
None |
AWS credential profile |
endpoint_url |
str | None |
None |
Custom endpoint (e.g., LocalStack) |
GCPKMSBackend
pip install "ecl_crypto_utility[gcp]"
from ecl_crypto_utility.kms.gcp import GCPKMSBackend
backend = GCPKMSBackend(project_id="my-project", location_id="us-east1", key_ring_id="my-keyring")
| Parameter | Type | Default | Description |
|---|---|---|---|
project_id |
str |
required | GCP project ID |
location_id |
str |
required | Cloud KMS location |
key_ring_id |
str |
required | Key ring name |
The key_id in methods is the crypto key name within the key ring.
VaultKMSBackend
pip install "ecl_crypto_utility[vault]"
from ecl_crypto_utility.kms.vault import VaultKMSBackend
backend = VaultKMSBackend(vault_url="https://vault.example.com:8200", token="hvs.my-token")
| Parameter | Type | Default | Description |
|---|---|---|---|
vault_url |
str |
required | Vault server URL |
token |
str | None |
None |
Auth token |
mount_point |
str |
"transit" |
Transit engine mount |
namespace |
str | None |
None |
Vault namespace (enterprise) |
Creating a Custom Backend
from ecl_crypto_utility.kms.base import KMSBackend
from ecl_crypto_utility.symmetric import generate_key
class MyCustomKMSBackend(KMSBackend):
def encrypt_key(self, key_material: bytes, key_id: str) -> str: ...
def decrypt_key(self, encrypted_key: str, key_id: str) -> bytes: ...
def generate_data_key(self, key_id: str) -> tuple[bytes, str]:
plaintext_key = generate_key()
encrypted_key = self.encrypt_key(key_material=plaintext_key, key_id=key_id)
return (plaintext_key, encrypted_key)
Exception Mapping
| Provider Exception | Mapped To |
|---|---|
NotFoundException / NotFound / InvalidPath |
KMSKeyNotFoundError |
AccessDeniedException / PermissionDenied / Forbidden |
KMSAuthenticationError |
DependencyTimeoutException / ServiceUnavailable / VaultDown |
KMSUnavailableError |
| Missing SDK (boto3, google-cloud-kms, hvac) | UnsupportedBackendError |
DEK Caching
In-memory LRU cache for decrypted DEKs to reduce KMS call latency.
API
class DEKCache(max_size: int = 100, ttl_seconds: int = 300)
| Method/Property | Signature | Description |
|---|---|---|
get |
get(encrypted_dek: str) -> bytes | None |
Retrieve cached DEK or None |
put |
put(encrypted_dek: str, plaintext_dek: bytes) -> None |
Cache a DEK; LRU eviction if full |
invalidate |
invalidate(encrypted_dek: str) -> None |
Remove a specific entry |
clear |
clear() -> None |
Remove all entries |
size |
@property -> int |
Current entry count |
Behavior
- LRU eviction: When at
max_size, the least recently used entry is evicted.get()moves entries to most-recently-used. - TTL expiration: Entries expire after
ttl_seconds. Expired entries are cleaned up lazily onget(). - Thread-safe: All operations protected by
threading.Lock.
Tuning
| Parameter | Too Small | Too Large | Default |
|---|---|---|---|
max_size |
Frequent KMS calls | More DEKs in memory | 100 |
ttl_seconds |
Frequent KMS calls | Longer key exposure | 300 (5 min) |
Security Notes
- Plaintext DEKs in process memory. Minimize exposure with shorter
ttl_seconds. - No disk persistence. Cleared on process exit.
- Call
cache.clear()on key rotation. - DEK values are never logged.
Audit Logging
Structured JSON audit trail for all KMS operations via ecl_logging_utility. Audit entries flow through the same pipeline as application logs (OpenSearch, Slack, etc.).
API
from ecl_crypto_utility import log_kms_operation
log_kms_operation(
operation="encrypt_dek", # "encrypt_dek" | "decrypt_dek" | "generate_dek"
key_id="my-key-id",
backend_type="AWSKMSBackend",
success=True,
metadata={"request_id": "abc-123"}, # optional
)
EnvelopeEncryptor calls this automatically on every encrypt() and decrypt(). No setup required.
Log Entry Format
{
"operation": "encrypt_dek",
"key_id": "arn:aws:kms:us-east-1:123:key/abc",
"backend": "AWSKMSBackend",
"success": true,
"timestamp": "2026-04-08T12:00:00+00:00"
}
Success logs at WARNING, failure at ERROR (with metadata.error).
Security Notes
- Audit entries include key IDs, never plaintext key material.
- All timestamps are UTC (ISO 8601).
- Do not disable audit logging in production.
- Avoid placing sensitive data in the
metadatafield.
Exceptions
All exceptions inherit from ECLCryptoError. Every exception accepts message and optional cause (chains via __cause__).
Hierarchy
ECLCryptoError
├── EncryptionError
├── DecryptionError
├── SigningError
├── VerificationError
├── HashingError
├── KeyDerivationError
├── KeyGenerationError
├── TokenGenerationError
├── KMSError
│ ├── KMSUnavailableError
│ ├── KMSAuthenticationError
│ └── KMSKeyNotFoundError
├── EnvelopeError
├── DEKCacheError
├── InvalidParameterError
└── UnsupportedBackendError
Which Exceptions to Catch
| Scenario | Catch |
|---|---|
| Any crypto operation | ECLCryptoError |
| Symmetric encrypt/decrypt | InvalidParameterError, EncryptionError, DecryptionError |
| RSA encrypt/decrypt | EncryptionError, DecryptionError |
| ECDSA sign/verify | SigningError, VerificationError |
| Password/data hashing | HashingError |
| Key derivation | KeyDerivationError |
| Token generation | TokenGenerationError |
| Envelope encryption | EnvelopeError, KMSError (and subclasses) |
| KMS backend instantiation | UnsupportedBackendError |
| KMS retryable errors | KMSUnavailableError (retryable); KMSAuthenticationError, KMSKeyNotFoundError (not retryable) |
Usage
from ecl_crypto_utility import decrypt
from ecl_crypto_utility.exceptions import DecryptionError, InvalidParameterError
try:
plaintext = decrypt(encrypted_data=ciphertext, key=key)
except InvalidParameterError as exc:
print(f"Invalid key: {exc}")
except DecryptionError as exc:
print(f"Decryption failed: {exc}")
if exc.cause:
print(f"Root cause: {exc.cause}")
Security Notes
- Exception messages may contain operational details. Do not expose them to end users.
KMSUnavailableErroris retryable (with exponential backoff). Other KMS errors are configuration issues.
Changelog
[1.0.0] - 2026-04-08
Fixed:
- Bug: duplicate
NotFoundExceptionin AWS KMS exception translation.
Added:
- Envelope field validation on decrypt. Master key length validation in auto-config.
- DEK rotation tests, config tests, envelope validation tests. 208 total tests, 95%+ coverage.
[0.4.0] - 2026-04-08
Changed:
- All
info-level logs downgraded todebugto prevent flooding consuming application logs. - Audit logging now uses
ecl_logging_utilityinstead of a dedicated Python standard logger. - Audit success entries log at
WARNINGlevel. Failure entries log atERROR.
[0.3.0] - 2026-04-08
Added:
- DEK reuse with configurable rotation. A single DEK is reused across multiple
encrypt()calls and rotated after a configurable number of uses or age, reducing KMS calls from one-per-encrypt to one-per-rotation-cycle. - New ENV variables:
ECL_CRYPTO_DEK_REUSE_ENABLED(default:true),ECL_CRYPTO_DEK_MAX_USES(default:10000),ECL_CRYPTO_DEK_MAX_AGE_SECONDS(default:300).
[0.2.0] - 2026-04-08
Added:
- Auto-configuration via environment variables. Top-level
encrypt()anddecrypt()now use envelope encryption configured entirely from ENV vars. config.pymodule with thread-safe lazy singleton for theEnvelopeEncryptor.
Changed:
- Top-level
encrypt()/decrypt()are now auto-configured envelope encryption functions. Direct symmetric functions moved toecl_crypto_utility.symmetric. - Replaced Python standard
loggingwithecl_logging_utility. - Renamed package directory from
ecl-crypto-utilitytoecl_crypto_utility. - Changed license from MIT to Proprietary.
[0.1.0] - 2026-04-08
Added:
- Symmetric encryption (AES-256-GCM), asymmetric encryption (RSA-4096-OAEP), digital signatures (ECDSA SECP384R1).
- Password hashing (Argon2id), integrity hashing (SHA-256), HMAC-SHA256.
- Key derivation (HKDF-SHA256, PBKDF2-HMAC-SHA256), secure token generation.
- Envelope encryption with pluggable KMS backends (AWS, GCP, Vault, Local).
- DEK caching with LRU eviction and TTL. Structured audit logging.
- Full exception hierarchy. 191 tests with 95%+ coverage.
License
Proprietary. Copyright (c) 2026 ECL. All rights reserved. See LICENSE for details.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ecl_crypto_utility-1.0.0.tar.gz.
File metadata
- Download URL: ecl_crypto_utility-1.0.0.tar.gz
- Upload date:
- Size: 69.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
669b245b6eb978918092efb43335df5e3a5ca7a803f718b508890161db658168
|
|
| MD5 |
292c153fcb8094ec1bac436162d6521d
|
|
| BLAKE2b-256 |
930963c124adc3177cfff8d1a4bb3ef60adafd3543e116174579a66435de514e
|
File details
Details for the file ecl_crypto_utility-1.0.0-py3-none-any.whl.
File metadata
- Download URL: ecl_crypto_utility-1.0.0-py3-none-any.whl
- Upload date:
- Size: 39.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1533b4c9d88fc1fe3676285640da23fd20d2d11f1dd3e8b592efca41058fef4b
|
|
| MD5 |
60f2fbc791132d7b8b5507e85fabdf5c
|
|
| BLAKE2b-256 |
42b49e4b15c246e401517c106b2b42c97bb0395899194393c4cfab2a2d7eefee
|