Metadata-Version: 2.4
Name: nx.v2-native
Version: 2.0.1
Summary: Native cryptographic library for authenticated encryption and token management
License: MIT
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: C++
Classifier: Topic :: Security :: Cryptography
Requires-Python: >=3.10
Description-Content-Type: text/markdown

# 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

```bash
pip install nx.v2-native
```

## Quick Start

```python
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** -- `KeyRing` instance (required)
- **aad** -- additional authenticated data (`str`, `dict`, or `None`)
- **metadata** -- `dict` of string key-value pairs embedded in the token

```python
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.

```python
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:

```python
{
    "data": "hello",
    "metadata": {},
    "key_id": "key-2026",
    "deterministic": False,
}
```

### Key Management

#### `generate_key()`

Returns a cryptographically random 32-byte `bytes` object.

```python
key = generate_key()
```

#### `derive_key_from_password(password, salt_hex="", N=131072, r=8, p=1)`

Derives a key from a password using scrypt.

```python
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.

```python
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.

```python
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.

```python
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.

```python
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):

```python
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.

```python
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.

```python
key_id = peek_key_id(token)  # "my-key"
```

#### `decode_token(token)`

Decode a token into its raw components. Returns `None` on invalid tokens.

```python
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`.

```python
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](https://www.npmjs.com/package/nx.v2-native) package. You can encrypt in Python and decrypt in Node.js, or vice versa.

```python
# Python
token = encrypt("hello from python", key_ring=ring)
```

```javascript
// 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
