Skip to main content

Production-grade, framework-agnostic Python cryptographic utility package

Project description

Cryptographic Utilities for ECL microservices

Production-grade, framework-agnostic Python cryptographic utility package.

Python License: Proprietary Coverage PyPI

Table of Contents


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() raises VerificationError on invalid signatures -- it never returns False.
  • 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:

  1. Password hashing (Argon2id) -- memory-hard, for storing user passwords
  2. Integrity hashing (SHA-256) -- fast, for verifying data has not been tampered with
  3. 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 HashingError on mismatch -- they never return False.
  • verify_data_integrity() and hmac_verify() use hmac.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_pbkdf2 or hash_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

  1. Generate a new 256-bit DEK.
  2. Encrypt the plaintext with the DEK (AES-256-GCM).
  3. Wrap the DEK via the KMS backend.
  4. Build a JSON envelope and Base64-encode it.
  5. Emit an audit log entry.

Decryption Flow

  1. Base64-decode and parse the envelope JSON.
  2. Validate the envelope version.
  3. Check the DEK cache (if provided).
  4. On cache miss, unwrap the DEK via the KMS backend.
  5. Cache the DEK (if cache provided).
  6. Decrypt the data with the DEK.
  7. 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.
  • LocalKMSBackend is 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 on get().
  • 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 metadata field.

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.
  • KMSUnavailableError is retryable (with exponential backoff). Other KMS errors are configuration issues.

Changelog

[1.0.0] - 2026-04-08

Fixed:

  • Bug: duplicate NotFoundException in 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 to debug to prevent flooding consuming application logs.
  • Audit logging now uses ecl_logging_utility instead of a dedicated Python standard logger.
  • Audit success entries log at WARNING level. Failure entries log at ERROR.

[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() and decrypt() now use envelope encryption configured entirely from ENV vars.
  • config.py module with thread-safe lazy singleton for the EnvelopeEncryptor.

Changed:

  • Top-level encrypt() / decrypt() are now auto-configured envelope encryption functions. Direct symmetric functions moved to ecl_crypto_utility.symmetric.
  • Replaced Python standard logging with ecl_logging_utility.
  • Renamed package directory from ecl-crypto-utility to ecl_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

ecl_crypto_utility-1.0.0.tar.gz (69.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ecl_crypto_utility-1.0.0-py3-none-any.whl (39.5 kB view details)

Uploaded Python 3

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

Hashes for ecl_crypto_utility-1.0.0.tar.gz
Algorithm Hash digest
SHA256 669b245b6eb978918092efb43335df5e3a5ca7a803f718b508890161db658168
MD5 292c153fcb8094ec1bac436162d6521d
BLAKE2b-256 930963c124adc3177cfff8d1a4bb3ef60adafd3543e116174579a66435de514e

See more details on using hashes here.

File details

Details for the file ecl_crypto_utility-1.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for ecl_crypto_utility-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1533b4c9d88fc1fe3676285640da23fd20d2d11f1dd3e8b592efca41058fef4b
MD5 60f2fbc791132d7b8b5507e85fabdf5c
BLAKE2b-256 42b49e4b15c246e401517c106b2b42c97bb0395899194393c4cfab2a2d7eefee

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page