# ENCRYPTION_SERVICE

AES-256-CTR encryption/decryption utilities with deterministic IV generation for cross-language interoperability.

## Installation

Add to your `Cargo.toml`:

```toml
[dependencies]
encryption_service = "0.1"
```

Or use local path:
```toml
encryption_service = { path = "path/to/encryption_service" }
```

## Quick Start

### Set environment variables in .env
```bash
ENCRYPTION_KEY=your-secret-phrase   # MUST be ≥ 32 bytes, cryptographically random
```

### Import and use
```rust
use encryption_service::encryption_service::Aes256CtrEncryption;

let token = Aes256CtrEncryption::encrypt_content("s3cr3t").await?;
let plain = Aes256CtrEncryption::decrypt_content(&token).await?;

let payload = serde_json::json!({
    "db": {
        "pass": Aes256CtrEncryption::encrypt_content("hunter2").await?
    }
});
let pw = Aes256CtrEncryption::decrypt_json_field(&payload, "db.pass").await?;
```

⚠️ **SECURITY**: `ENCRYPTION_KEY` must be ≥ 32 bytes of cryptographically random data.
Never use a human-chosen password. A warning logs if key is too short.

## Configuration

### Environment Variables

| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `ENCRYPTION_KEY` | Yes | — | AES-256 key material (≥ 32 bytes, cryptographically random) |

## Key Features

1. **Deterministic IV Generation** — Same plaintext always produces same ciphertext (cross-language compatible)
2. **AES-256-CTR Mode** — High-performance encryption with no padding issues
3. **SHA-256 Key Derivation** — Compatible with Python sibling implementation
4. **Pure Cryptographic Functions** — No side effects, thread-safe, deterministic
5. **JSON Field Helpers** — `encrypt_to_json_field()` and `decrypt_json_field()` for nested JSON paths

## API Reference

### Aes256CtrEncryption::encrypt_content(plaintext: &str) -> Result<String>
Encrypts arbitrary plaintext to a base64 string.
- `plaintext`: String to encrypt
- Returns: Base64-encoded ciphertext

### Aes256CtrEncryption::decrypt_content(encoded_ciphertext: &str) -> Result<String>
Decrypts base64-encoded ciphertext back to plaintext.
- `encoded_ciphertext`: Base64-encoded ciphertext from encrypt()
- Returns: Original plaintext string

### Aes256CtrEncryption::encrypt_to_json_field(plaintext: &str, json_path: &str) -> Result<serde_json::Value>
Encrypts plaintext and embeds at a dot-notation JSON path.
- `plaintext`: String to encrypt
- `json_path`: Dot-notation path (e.g. "user.credentials.password")
- Returns: JSON value with encrypted field embedded

### Aes256CtrEncryption::decrypt_json_field(json_value: &serde_json::Value, json_path: &str) -> Result<String>
Decrypts a single nested field in a JSON object.
- `json_value`: Value containing encrypted fields
- `json_path`: Dot-notation path (e.g. "user.credentials.password")
- Returns: Decrypted plaintext string

## Security Notes

- **Key Requirements**: Use cryptographically random keys ≥ 32 bytes. SHA-256 derivation provides no brute-force resistance.
- **Deterministic Encryption**: Identical plaintexts produce identical ciphertexts — acceptable for this use case.
- **Cross-Language Interop**: IV derivation matches Python implementation exactly.
- No KDF (no PBKDF2/scrypt/argon2) — uses raw SHA-256 for Python compatibility.

## Performance

```
┌──────────────────────┬──────────┬──────────┬─────────────┬──────────┬──────────┐
│ Test                 │ Ops      │ Time     │ Throughput  │ Target   │ Status   │
├──────────────────────┼──────────┼──────────┼─────────────┼──────────┼──────────┤
│ Encrypt (async)      │  1,000   │ 0.038 s  │ 26,253/sec  │ 10k/sec  │ ✅ PASS  │
│ Decrypt (async)      │  1,000   │ 0.028 s  │ 35,920/sec  │ 10k/sec  │ ✅ PASS  │
│ Round-trip           │    100   │ 0.058 ms │    avg/call │  < 1 ms  │ ✅ PASS  │
│ JSON field decrypt   │    500   │ 0.014 s  │ 37,171/sec  │ 10k/sec  │ ✅ PASS  │
│ Encrypt (concurrent)│  1,000   │ 0.092 s  │ 11,052/sec  │ 10k/sec  │ ✅ PASS  │
│ Encrypt (4-tasks)   │  2,000   │ 0.197 s  │ 10,163/sec  │ 10k/sec  │ ✅ PASS  │
└──────────────────────┴──────────┴──────────┴─────────────┴──────────┴──────────┘
System: Ubuntu 24.04.4 LTS | AMD Ryzen 7 5800H (16 cores, 13Gi RAM) | Rust 1.94.0
Tested: 2026-04-03 16:56 UTC
```

**Note:** Multi-task throughput is limited by the async runtime — AES-CTR is CPU-bound.

## Error Handling

The library returns `anyhow::Result<T>`:
- `anyhow::anyhow!("ENCRYPTION_KEY not set")` if not set (raised on first crypto call, lazy)
- `anyhow::anyhow!("ENCRYPTION_KEY too short")` if key is shorter than 32 bytes
- `anyhow::anyhow!("Ciphertext too short")` if ciphertext is shorter than 16 bytes
- `anyhow::anyhow!("Invalid JSON path")` if path doesn't exist in the provided JSON
- `anyhow::anyhow!("expected str at path")` if JSON path resolves to a non-string leaf

A warning is logged if `ENCRYPTION_KEY` is shorter than 32 bytes.

## Test Coverage

```bash
cargo test --lib
```

| Function | Tier | Tests | What is tested |
|----------|------|-------|----------------|
| `encrypt_content()` | 1 | 4 | non-empty base64, determinism, different inputs, empty string |
| `decrypt_content()` | 1 | 4 | round-trip, empty string, too-short input, unicode |
| `decrypt_json_field()` | 1 | 4 | nested path, single-level, missing key, wrong type |
| `encrypt_to_json_field()` | 1 | 3 | nested path, single-level, empty path error |
| `_derive_256bit_key()` | 2 | 3 | 32 bytes output, determinism, different passwords |

## Dependencies

```toml
[dependencies]
aes = "0.8"
base64 = "0.22"
sha2 = "0.10"
ctr = "0.9"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
anyhow = "1.0"
tokio = { version = "1", features = ["full"] }
```