Crypto โ Integration Guide
Overview
The crypto service is the platform's centralized encryption microservice โ a Rust TCP worker that provides AES-256-GCM file encryption for any backend consumer. Architecturally, it mirrors the imaging service: a dedicated TCP microservice that other services call for CPU-bound operations, keeping the main GraphQL subgraphs non-blocking.
The crypto service has zero knowledge of the files it encrypts. It is stateless โ callers manage their own keys. This design ensures that no single service holds both the ciphertext and the key, reinforcing the platform's E2EE security model.
Architecture at a Glance
| Component | Technology | Port | Access |
|---|---|---|---|
| Framework | Rust (Tokio TCP) | 30022 |
Internal only |
| Algorithm | AES-256-GCM | โ | 256-bit key |
| Key Gen | OS CSPRNG | โ | Via OsRng |
| Library | ngwenya_crypto |
โ | WASM-compilable |
Why Rust? Crypto operations are CPU-bound โ Rust is ~10ร faster than Node.js for AES-GCM. Rust's memory safety also eliminates buffer overflow vulnerabilities when handling key material. The library target (
src/lib.rs) can compile to WASM for frontend reuse โ one implementation across the full stack.
Cross-Service Consumer Map
The crypto service is designed for cross-service reuse across the entire Ngwenya platform:
| Consumer | Use Case | Status |
|---|---|---|
| uChat | E2EE file attachments in encrypted conversations | โ Live |
| uMail | Encrypted email attachments | ๐ Future |
| uCloud | Private cloud file storage (25 MB+ files) | ๐ Future |
| Webhooks | Encrypted webhook payloads | ๐ Future |
| Arcade Gaming | Digital key encryption at rest | ๐ Phase 2 |
uCloud: When a Visitor tries to send a file exceeding 25 MB via uChat, the error message directs them to uCloud Private Storage โ a future universal cloud service that will use this same crypto microservice for encryption.
TCP Protocol
The service accepts newline-delimited JSON over TCP. Each request is a single JSON object followed by \n, and the response follows the same format.
Encrypt File
// Request
{"action": "encrypt_file", "data": [104, 101, 108, 108, 111]}
// Response
{
"ciphertext": [...],
"key": "a1b2c3d4e5f6...",
"iv": "d4e5f6...",
"algorithm": "aes-256-gcm",
"encrypted": true
}
The key and iv are hex-encoded for safe transport. An optional key field on the request allows callers to provide their own key instead of generating one.
Decrypt File
// Request
{"action": "decrypt_file", "ciphertext": [...], "key": "a1b2c3...", "iv": "d4e5f6..."}
// Response
{"data": [104, 101, 108, 108, 111], "decrypted": true}
Tamper Detection: If the ciphertext has been modified, decryption fails immediately with an authentication tag mismatch error. This is a security property of GCM mode โ the server cannot silently return corrupted data.
Generate Key
// Request
{"action": "generate_key"}
// Response
{"key": "a1b2c3d4e5f6...", "algorithm": "aes-256-gcm"}
Health Check
// Request
{"action": "health"}
// Response
{"status": "ok", "algorithm": "aes-256-gcm"}
Security Model
| Property | Value |
|---|---|
| Algorithm | AES-256-GCM (authenticated encryption) |
| Key size | 256 bits (32 bytes) |
| Nonce (IV) | 96 bits (12 bytes), randomly generated per operation |
| Authentication tag | 128 bits (appended to ciphertext by aes-gcm crate) |
| Key generation | OS CSPRNG via OsRng โ suitable for production |
| Tamper detection | GCM tag verification fails if ciphertext is modified |
| Key storage | None โ the service is stateless, callers manage their own keys |
| Tag verification | Constant-time (provided by the aes-gcm crate) |
Key Interoperability
The crypto service uses the same algorithm (AES-256-GCM) as:
- The frontend
$lib/cryptomodule (Web Crypto API) - Malet Vaults (Node.js
cryptomodule)
This means keys generated by any of these implementations can be used interchangeably. A file encrypted by the frontend can be decrypted by the backend service, and vice versa.
Frontend Integration: `$lib/crypto`
The frontend uses a shared $lib/crypto module (TypeScript) that mirrors the crypto service's strategy pattern โ identical to how the imaging service uses RemoteStrategy vs LocalStrategy.
Strategy Pattern
| Strategy | Environment | Implementation |
|---|---|---|
WebCryptoStrategy |
Production (browsers) | AES-256-GCM via window.crypto.subtle |
NoOpStrategy |
Dev/testing/SSR | Passthrough (no encryption) |
WasmStrategy |
Future | Compile lib.rs to WASM |
Usage
import { fileCrypto } from '$lib/crypto';
// Encrypt a file
const result = await fileCrypto.encrypt(fileBuffer);
// result.ciphertext โ upload to S3
// result.key, result.iv โ wrap in Megolm envelope via encrypted_body
// Decrypt a file
const plaintext = await fileCrypto.decrypt(ciphertext, key, iv);
Mobile Support
The Web Crypto API is available in:
- iOS: WKWebView supports
window.crypto.subtle - Android: WebView supports
window.crypto.subtle
For future native (non-WebView) apps, the Rust lib.rs can compile to native libraries via cargo-ndk (Android) and as a static lib (iOS).
WASM Dual-Target Architecture
The src/lib.rs file is a library crate (ngwenya_crypto) that can be compiled to multiple targets:
graph TB
A["src/lib.rs<br/>(ngwenya_crypto)"] --> B["Native Binary<br/>TCP Service (port 30022)"]
A --> C["WASM Module<br/>Browser/Node.js"]
A --> D["Android Native<br/>via cargo-ndk"]
A --> E["iOS Static Lib<br/>via cargo-lipo"]
B --> F["Backend Consumers<br/>uMail, Webhooks"]
C --> G["Frontend $lib/crypto<br/>Replaces WebCryptoStrategy"]
D --> H["Android App"]
E --> I["iOS App"]
This architecture means a single Rust implementation powers:
- The TCP microservice (for backend consumers like uMail)
- The browser (via WASM, replacing
WebCryptoStrategy) - Native mobile apps (via
cargo-ndk/ static lib)
File Attachments Flow (uChat)
The crypto service is the backbone of uChat's file attachment system. Here's the complete lifecycle:
sequenceDiagram
participant V as Visitor (Browser)
participant FC as $lib/crypto
participant M as Media Service
participant S3 as S3 / CDN
participant UC as uChat Mutation
participant R as Redis Pub/Sub
participant V2 as Recipient
V->>FC: encrypt(fileBuffer)
FC-->>V: {ciphertext, key, iv}
V->>M: requestUploadUrl(filename, mime)
M-->>V: {uploadUrl, fileId}
V->>S3: PUT encrypted blob
V->>M: confirmUpload(fileId)
M-->>V: {cdnUrl}
V->>UC: sendMessage(FILE, cdnUrl, encryptedBody)
UC->>R: PUBLISH new_message + file_metadata
R->>V2: WebSocket delivery
V2->>S3: GET encrypted blob
V2->>FC: decrypt(blob, key, iv)
FC-->>V2: plaintext file
Security Boundaries
- Client-side encryption: The file is encrypted before leaving the browser
- Opaque storage: S3 stores an encrypted blob โ the storage provider cannot read the file
- Key wrapping: The AES key + IV are wrapped in the Megolm
encrypted_bodyโ the uChat server stores this but cannot decrypt it - Recipient decryption: Only the recipient's browser (with the Megolm session key) can extract the AES key and decrypt the file
Size Limit
Files are capped at 25 MB (26,214,400 bytes), enforced at the sendMessage mutation level. Files exceeding this limit receive:
"File exceeds 25 MB limit. Large file sharing will be available via uCloud Private Storage."
Environment Variables
| Variable | Required | Default | Description |
|---|---|---|---|
CRYPTO_SERVICE_PORT_TCP |
No | 30022 |
TCP listener port |
RUST_LOG |
No | crypto_service=info |
Log level filter |
File Reference
| File | Purpose |
|---|---|
apps/crypto/Cargo.toml |
Dependencies: aes-gcm, rand, tokio, serde |
apps/crypto/src/lib.rs |
Core crypto primitives (library target, WASM-compilable) |
apps/crypto/src/handler.rs |
TCP JSON-framed request/response handler |
apps/crypto/src/main.rs |
TCP bootstrap entrypoint |
apps/crypto/README.md |
Protocol docs, security model, multi-target notes |
Frontend
| File | Purpose |
|---|---|
src/lib/crypto/strategies/interface.ts |
FileCryptoStrategy interface |
src/lib/crypto/strategies/webCrypto.ts |
Production AES-256-GCM via Web Crypto API |
src/lib/crypto/strategies/noop.ts |
Dev/testing passthrough |
src/lib/crypto/fileCrypto.ts |
Strategy factory (auto-selects per environment) |
src/lib/crypto/index.ts |
Barrel export |
Testing
Unit Tests
cd apps/crypto && cargo test
10 tests covering:
- Key generation (length, uniqueness)
- Encrypt/decrypt round-trip (with generated and provided keys)
- Wrong key rejection (authentication tag mismatch)
- Tampered ciphertext rejection
- Large file round-trip (1 MB)
- Empty file round-trip
- Invalid hex input
- Wrong key length
Integration Tests
cd apps/uchat && cargo test --test file_attachments
5 tests validating FILE message type:
- Required field validation (
file_url) - 25 MB size limit enforcement
- TEXT message backward compatibility
- Redis pub/sub payload structure
Related
uChat โ E2EE Messenger โ Primary consumer for E2EE file attachments
Imaging โ Mirrors the TCP microservice architecture pattern (strategy adapter)
Media Infrastructure โ Handles presigned S3 upload URLs for encrypted file blobs
Malet Vaults & Verification โ Uses the same AES-256-GCM algorithm for encrypted merchant settings
Arcade Gaming โ Future consumer for digital key encryption at rest
User Guide: uChat Messenger โ Visitor-facing guide to encrypted messaging
uChat Voice Messages Architecture โ Technical implementation of E2EE voice messaging.