Native cryptographic library for authenticated encryption and token management
Project description
nx.v2-native
A high-performance native Python library for authenticated encryption, key management, and token-based secret handling. All cryptographic operations run in C++ via OpenSSL.
Features
- ChaCha20-Poly1305 authenticated encryption with AAD
- Deterministic encryption (SIV-style) for indexable ciphertext
- Key derivation via scrypt (password) and HKDF-SHA256 (subkeys)
- Token format -- self-contained, versioned, base64url-encoded binary tokens
- Key rotation -- KeyRing with multiple keys + seamless migration
- Replay protection -- built-in TTL-based replay guard
- Cross-compatible -- tokens produced here can be decrypted by the Node.js nx.v2-native package and vice versa
Install
pip install nx.v2-native
Quick Start
from nx import generate_key, encrypt, decrypt, KeyRing
# Create a key ring and add a key
ring = KeyRing()
ring.add("key-2026", generate_key())
# Encrypt
token = encrypt("sensitive data", key_ring=ring)
# -> "nx.v2.AgAHa2V5LTIwMjY..."
# Decrypt
result = decrypt(token, key_ring=ring)
print(result["data"]) # "sensitive data"
API
Encryption & Decryption
encrypt(plaintext, *, key_ring, aad=None, metadata=None)
Encrypts a value and returns an nx.v2. token string.
- plaintext --
str - key_ring --
KeyRinginstance (required) - aad -- additional authenticated data (
str,dict, orNone) - metadata --
dictof string key-value pairs embedded in the token
token = encrypt("hello", key_ring=ring, aad="user:123", metadata={"purpose": "greeting"})
encrypt_deterministic(plaintext, *, key_ring, aad=None, metadata=None)
Same as encrypt, but produces identical tokens for identical inputs. Useful for indexing or deduplication.
a = encrypt_deterministic("hello", key_ring=ring)
b = encrypt_deterministic("hello", key_ring=ring)
assert a == b
decrypt(token, *, key_ring=None, global_decryptor=None, aad=None)
Decrypts a token. Raises RuntimeError on failure.
Returns:
{
"data": "hello",
"metadata": {},
"key_id": "key-2026",
"deterministic": False,
}
Key Management
generate_key()
Returns a cryptographically random 32-byte bytes object.
key = generate_key()
derive_key_from_password(password, salt_hex="", N=131072, r=8, p=1)
Derives a key from a password using scrypt.
result = derive_key_from_password("my-password")
key = result["key"]
salt = result["salt_hex"]
# Deterministic re-derivation
same = derive_key_from_password("my-password", salt_hex=salt)
assert result["key"] == same["key"]
derive_subkey(master_key, context, salt=None)
Derives a subkey using HKDF-SHA256.
master = generate_key()
db_key = derive_subkey(master, "database")
api_key = derive_subkey(master, "api-tokens")
KeyRing
Manages multiple encryption keys with a designated current key.
from nx import KeyRing, generate_key
ring = KeyRing()
ring.add("v1", generate_key())
ring.add("v2", generate_key())
ring.set_current("v2")
ring.current_id # "v2"
ring.size # 2
ring.has("v1") # True
ring.resolve("v1") # bytes
ring.key_ids # ["v2", "v1"] -- current first
ring.remove("v1") # removes key
ring.wipe_all() # clears all keys
GlobalDecryptor
Resolves keys across multiple KeyRings. Useful for multi-tenant or multi-service decryption.
from nx import GlobalDecryptor
service_a = KeyRing()
service_a.add("a-key", generate_key())
service_b = KeyRing()
service_b.add("b-key", generate_key())
gd = GlobalDecryptor()
gd.add_ring(service_a)
gd.add_ring(service_b)
# Decrypts tokens from either service
result = decrypt(token, global_decryptor=gd)
ReplayGuard
Prevents token reuse with an in-memory TTL store.
from nx import ReplayGuard
guard = ReplayGuard(ttl_seconds=300) # 5 minutes
# First use -- succeeds
result = guard.decrypt(token, key_ring=ring)
# Second use -- raises "nx: token has already been used."
guard.decrypt(token, key_ring=ring)
Custom store (e.g. Redis):
class RedisStore:
def __init__(self, client):
self.r = client
def has(self, key):
return self.r.exists(key)
def add(self, key, ttl_seconds):
self.r.set(key, "1", ex=int(ttl_seconds))
guard = ReplayGuard(store=RedisStore(redis_client))
Key Rotation
migrate(token, *, key_ring, aad=None, metadata=None)
Re-encrypts a token under the current key if it was encrypted with an older key. Returns the original token unchanged if already current.
ring = KeyRing()
ring.add("v1", old_key)
ring.add("v2", new_key)
ring.set_current("v2")
migrated = migrate(old_token, key_ring=ring)
# migrated is now encrypted under "v2"
Utilities
peek_key_id(token)
Extract the key ID from a token without decrypting. Returns None on invalid tokens.
key_id = peek_key_id(token) # "my-key"
decode_token(token)
Decode a token into its raw components. Returns None on invalid tokens.
parsed = decode_token(token)
# {
# "version": 2,
# "flags": 0,
# "deterministic": False,
# "key_id": "my-key",
# "nonce": b"...",
# "tag": b"...",
# "ciphertext": b"...",
# "metadata": {},
# }
token_fingerprint(token)
Get the fingerprint (keyId:nonceHex) of a token. Returns None on invalid tokens.
wipe_buffer(buf)
Securely zero a writable buffer using OpenSSL's OPENSSL_cleanse.
key = bytearray(generate_key())
wipe_buffer(key) # all bytes are now 0x00
Token Format
Tokens are prefixed with nx.v2. followed by a base64url-encoded binary payload:
| Field | Size | Description |
|---|---|---|
| version | 1 byte | 0x02 |
| flags | 1 byte | 0x01 = deterministic |
| keyId length | 1 byte | 1--255 |
| keyId | variable | UTF-8 encoded |
| nonce | 12 bytes | Random or SIV-derived |
| auth tag | 16 bytes | Poly1305 tag |
| metadata length | 2 bytes | Big-endian uint16 |
| metadata | variable | Canonical JSON (sorted keys) |
| ciphertext | remaining | Encrypted payload |
Cross-Compatibility
Tokens produced by this Python library are fully compatible with the Node.js nx.v2-native package. You can encrypt in Python and decrypt in Node.js, or vice versa.
# Python
token = encrypt("hello from python", key_ring=ring)
// Node.js
const result = decrypt(token, { keyRing: ring });
console.log(result.data); // "hello from python"
Security
- ChaCha20-Poly1305 AEAD with 12-byte nonce and 16-byte authentication tag
- HMAC-SHA256 for deterministic (SIV) nonce derivation
- scrypt for password-based key derivation with configurable cost
- HKDF-SHA256 for domain-separated subkey derivation
- OPENSSL_cleanse for secure memory wiping
- AAD is cryptographically bound -- tampering with keyId, metadata, or context is detected
- All crypto runs in compiled C++ via OpenSSL -- no Python crypto code
Platform Support
| Platform | Architecture | Status |
|---|---|---|
| Windows | x64 | Prebuilt |
| macOS | arm64 | Coming soon |
| macOS | x64 | Coming soon |
| Linux | x64 | Coming soon |
Requirements
- Python >= 3.10
License
MIT
Project details
Release history Release notifications | RSS feed
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 nx_v2_native-2.0.2.tar.gz.
File metadata
- Download URL: nx_v2_native-2.0.2.tar.gz
- Upload date:
- Size: 2.9 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8b269d51ee0a5a43c9548f31926a0699c293efe8aa9b9e501a116fd044955e64
|
|
| MD5 |
b444e5d8696d3738b9dcb2d40f63faf2
|
|
| BLAKE2b-256 |
fa192e7bd60ff90d9dbf51ab4d2171f50e0e66b6448f629515743507d7222cb0
|
File details
Details for the file nx_v2_native-2.0.2-cp312-cp312-win_amd64.whl.
File metadata
- Download URL: nx_v2_native-2.0.2-cp312-cp312-win_amd64.whl
- Upload date:
- Size: 2.9 MB
- Tags: CPython 3.12, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f86aa763f8ab8b3cf8f6954a1547df86f04dc13bd396734c54d9297364d35f9f
|
|
| MD5 |
41b27d1b050eae5d10f0c6f9af1ea719
|
|
| BLAKE2b-256 |
cdb29e229508c4aa2268e56ee3d05c3c3e3de8523abf5fbed54a548cdeaf77da
|