# Herolib Crypt
Comprehensive cryptography library for Rust providing asymmetric, symmetric, and HTTP signature functionality.
## Overview
`herolib-crypt` provides comprehensive cryptographic primitives for the HeroLib ecosystem with optional feature flags for selective compilation.
## Features
- **Asymmetric Cryptography**
- Ed25519 digital signatures (signing/verification)
- X25519 ECDH encryption (encrypt/decrypt)
- Dual keypair architecture (separate keys for signing and encryption)
- **Symmetric Cryptography**
- XChaCha20-Poly1305 authenticated encryption
- Argon2id password-based key derivation
- Secure key handling with automatic memory zeroization
- **HTTP Message Signatures**
- RFC 9421/9530 compliant request authentication
- Ed25519-based digital signatures for HTTP requests
- Replay protection and tampering detection
- **Ed25519 Keys Module**
- Fast, secure elliptic curve signatures
- Comprehensive key serialization utilities
- Constant-time operations for security
- **Unified Rhai Integration**: Single registration function for all modules
## Feature Flags
- `httpsig` (default): HTTP Message Signatures
- `rhai`: Enable Rhai scripting support
- `full`: Enable all features (`httpsig` + `rhai`)
**Note**: The `keys` module is always included as it's essential for core functionality.
## Installation
Add to your `Cargo.toml`:
```toml
[dependencies]
# Default: httpsig enabled
herolib-crypt = { workspace = true }
# Everything including Rhai
herolib-crypt = { workspace = true, features = ["full"] }
# Only core functionality (no HTTP signatures or Rhai)
herolib-crypt = { workspace = true, default-features = false }
```
## Quick Start
### Asymmetric: Generate a Keypair
```rust
use herolib_crypt::generate_keypair;
let keypair = generate_keypair()?;
// For signing (Ed25519)
println!("Signing Private: {}", keypair.private_key_hex);
println!("Signing Public: {}", keypair.public_key_hex);
// For encryption (X25519)
println!("Encryption Private: {}", keypair.encryption_private_key_hex);
println!("Encryption Public: {}", keypair.encryption_public_key_hex);
```
### Asymmetric: Sign and Verify Messages
```rust
use herolib_crypt::{generate_keypair, sign_message, verify_signature};
// Generate keypair
let keypair = generate_keypair()?;
// Sign a message (uses Ed25519 signing key)
let message = "Hello, world!";
let signature = sign_message(message, &keypair.private_key_hex)?;
// Verify the signature (uses Ed25519 public key)
let is_valid = verify_signature(message, &signature, &keypair.public_key_hex)?;
assert!(is_valid);
```
### Asymmetric: Encrypt and Decrypt Messages
```rust
use herolib_crypt::{generate_keypair, encrypt_message, decrypt_message};
// Alice and Bob generate their keypairs
let alice = generate_keypair()?;
let bob = generate_keypair()?;
// Alice encrypts a message for Bob (uses Bob's X25519 encryption public key)
let message = "Secret message";
let encrypted = encrypt_message(message, &bob.encryption_public_key_hex)?;
// Bob decrypts with his X25519 encryption private key
let decrypted = decrypt_message(&encrypted, &bob.encryption_private_key_hex)?;
assert_eq!(decrypted, message);
```
### Symmetric: Password-Based Encryption (Recommended)
```rust
use herolib_crypt::symmetric::{encrypt_with_password, decrypt_with_password};
// Encrypt with just a password - salt is handled automatically!
let encrypted = encrypt_with_password(b"secret data", "my-password")?;
// Decrypt with the same password
let decrypted = decrypt_with_password(&encrypted, "my-password")?;
assert_eq!(decrypted, b"secret data");
```
### Symmetric: String Encryption
```rust
use herolib_crypt::symmetric::{encrypt_string, decrypt_string};
// Encrypt string to base64
let encrypted = encrypt_string("Hello, World!", "my-password")?;
// Decrypt back to string
let decrypted = decrypt_string(&encrypted, "my-password")?;
assert_eq!(decrypted, "Hello, World!");
```
### Symmetric: Advanced - Random Key
```rust
use herolib_crypt::symmetric::{EncryptionKey, Cipher};
// Generate a random key (for programmatic key management)
let key = EncryptionKey::generate();
let cipher = Cipher::new(key);
// Encrypt and decrypt
let encrypted = cipher.encrypt(b"secret data")?;
let decrypted = cipher.decrypt(&encrypted)?;
```
### Ed25519 Keys Module
```rust
use herolib_crypt::keys::Ed25519Keypair;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Generate a keypair
let keypair = Ed25519Keypair::generate()?;
// Sign a message
let message = b"Hello, World!";
let signature = keypair.sign(message);
// Verify the signature
let public_key = keypair.public_key();
let valid = public_key.verify(message, &signature)?;
assert!(valid);
Ok(())
}
```
### Key Serialization
```rust
use herolib_crypt::keys::Ed25519Keypair;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let keypair = Ed25519Keypair::generate()?;
// Export as hex
let private_hex = keypair.to_hex();
let public_hex = keypair.to_public_key_hex();
// Import from hex
let restored = Ed25519Keypair::from_hex(&private_hex)?;
// Export as bytes
let private_bytes = keypair.to_bytes();
let public_bytes = keypair.to_public_key_bytes();
Ok(())
}
```
### HTTP Message Signatures
```rust
use herolib_crypt::keys::Ed25519Keypair;
use herolib_crypt::httpsig::HttpSigner;
use http::Request;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Generate keypair
let keypair = Ed25519Keypair::generate()?;
let signer = HttpSigner::new(keypair, "user-123");
// Sign an HTTP request
let body = b"{\"amount\": 100}";
let mut request = Request::post("https://api.example.com/payments")
.header("content-type", "application/json")
.body(body.to_vec())?;
signer.sign_request(&mut request, body)?;
// Request now has Signature-Input, Signature, and Content-Digest headers
Ok(())
}
```
### Verifying HTTP Signatures
```rust
use herolib_crypt::httpsig::HttpVerifier;
use herolib_crypt::keys::Ed25519PublicKey;
use http::Request;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Get the public key (e.g., from hex string stored in database)
let public_key_hex = "..."; // The signer's public key
let public_key = Ed25519PublicKey::from_hex(public_key_hex)?;
// Create verifier with a public key
let verifier = HttpVerifier::new()
.with_key(public_key)
.with_tolerance(60); // 1 minute tolerance
// Verify the request (with signature headers from client)
let body = b"{\"amount\": 100}";
let request = Request::post("https://api.example.com/payments")
.header("content-type", "application/json")
.header("signature-input", "sig1=(...)")
.header("signature", "sig1=:...:")
.header("content-digest", "sha-256=:...:")
.body(body.to_vec())?;
let result = verifier.verify_request(&request, body)?;
println!("✓ Verified! Key ID: {}", result.key_id);
Ok(())
}
```
## Rhai Integration
The crypt module provides unified Rhai registration:
```rust
use rhai::Engine;
use herolib_crypt::rhai::register_crypto_module;
let mut engine = Engine::new();
register_crypto_module(&mut engine)?;
```
This registers all functions from both `keys` and `httpsig` modules.
### Rhai Example
```rhai
// Generate keypair
let keypair = ed25519_generate();
let public_key = public_key(keypair);
// Sign HTTP request
let signer = httpsig_signer_new(keypair, "user-123");
let result = httpsig_sign(
signer,
"POST",
"/api/payments",
"api.example.com",
#{},
`{"amount": 100}`
);
// Verify signature
let verifier = httpsig_verifier_new();
let verifier = httpsig_verifier_with_key(verifier, public_key);
let headers = #{
"signature-input": result.signature_input,
"signature": result.signature,
"content-digest": result.content_digest
};
let verify_result = httpsig_verify(
verifier,
"POST",
"/api/payments",
"api.example.com",
headers,
`{"amount": 100}`
);
if verify_result.verified {
print(`✓ Verified by ${verify_result.key_id}`);
}
```
## Module Structure
```
herolib-crypt
├── asymmetric (always included)
│ ├── Ed25519 signing (delegates to keys module)
│ └── X25519 encryption
├── symmetric (always included)
│ ├── XChaCha20-Poly1305 encryption
│ └── Argon2id key derivation
├── keys (always included)
│ ├── Ed25519Keypair
│ ├── Ed25519PublicKey
│ ├── Signature
│ ├── KeyError
│ └── utility functions
├── httpsig (feature: httpsig)
│ ├── HttpSigner
│ ├── HttpVerifier
│ ├── HttpSigError
│ └── utility functions
└── rhai (feature: rhai)
├── register() - unified registration
├── keys::rhai::register()
└── httpsig::rhai::register()
```
## API Reference
### Asymmetric Key Generation
- `generate_keypair() -> CryptoResult<KeyPair>` - Generate a new dual keypair (Ed25519 + X25519)
- `public_key_from_private(private_key_hex) -> CryptoResult<String>` - Derive Ed25519 public key
- `encryption_public_key_from_private(encryption_private_key_hex) -> CryptoResult<String>` - Derive X25519 public key
### Asymmetric Signing (Ed25519)
- `sign_message(message, private_key_hex) -> CryptoResult<String>` - Sign a message
- `verify_signature(message, signature_hex, public_key_hex) -> CryptoResult<bool>` - Verify a signature
### Asymmetric Encryption (X25519 + ChaCha20-Poly1305)
- `encrypt_message(message, recipient_encryption_public_key_hex) -> CryptoResult<String>` - Encrypt for a recipient
- `decrypt_message(encrypted_hex, encryption_private_key_hex) -> CryptoResult<String>` - Decrypt a message
### Symmetric Encryption (Simple API)
- `encrypt_with_password(data, password) -> SymmetricResult<Vec<u8>>` - Encrypt with password
- `decrypt_with_password(encrypted, password) -> SymmetricResult<Vec<u8>>` - Decrypt with password
- `encrypt_string(text, password) -> SymmetricResult<String>` - Encrypt string to base64
- `decrypt_string(encrypted_base64, password) -> SymmetricResult<String>` - Decrypt base64 to string
### Symmetric Encryption (Advanced API)
- `EncryptionKey::generate() -> EncryptionKey` - Generate a random 256-bit key
- `EncryptionKey::derive_from_password(password, salt) -> SymmetricResult<EncryptionKey>` - Derive key from password
- `Cipher::new(key) -> Cipher` - Create a cipher
- `Cipher::encrypt(plaintext) -> SymmetricResult<Vec<u8>>` - Encrypt data
- `Cipher::decrypt(ciphertext) -> SymmetricResult<Vec<u8>>` - Decrypt data
### Keys Module
#### `Ed25519Keypair`
- `generate() -> Result<Ed25519Keypair, KeyError>` - Generate new keypair
- `from_bytes(bytes: &[u8]) -> Result<Ed25519Keypair, KeyError>` - Import from bytes
- `from_hex(hex: &str) -> Result<Ed25519Keypair, KeyError>` - Import from hex
- `sign(&self, message: &[u8]) -> Signature` - Sign message
- `verify(&self, message: &[u8], signature: &Signature) -> Result<bool, KeyError>` - Verify signature
- `public_key(&self) -> Ed25519PublicKey` - Get public key
- `to_bytes(&self) -> Vec<u8>` - Export private key as bytes
- `to_hex(&self) -> String` - Export private key as hex
- `to_public_key_bytes(&self) -> Vec<u8>` - Export public key as bytes
- `to_public_key_hex(&self) -> String` - Export public key as hex
#### Keys Utility Functions
- `generate_random_bytes(size: usize) -> Vec<u8>` - Generate cryptographically secure random bytes
- `encode_hex(data: &[u8]) -> String` - Encode bytes to hex string
- `decode_hex(hex: &str) -> Result<Vec<u8>, KeyError>` - Decode hex string to bytes
- `sha256_digest(data: &[u8]) -> Vec<u8>` - Compute SHA-256 hash
- `secure_compare(a: &[u8], b: &[u8]) -> bool` - Constant-time comparison
- `verify_ed25519(pubkey: &[u8], message: &[u8], signature: &[u8]) -> Result<bool, KeyError>` - Standalone verification
### HTTP Signatures Module
#### `HttpSigner`
- `new(keypair: Ed25519Keypair, key_id: impl Into<String>) -> Self` - Create new signer
- `with_headers(self, headers: Vec<String>) -> Self` - Add headers to sign
- `with_label(self, label: impl Into<String>) -> Self` - Set signature label (default: "sig1")
- `sign_request(&self, request: &mut Request<Vec<u8>>, body: &[u8]) -> Result<(), HttpSigError>` - Sign HTTP request
#### `HttpVerifier`
- `new() -> Self` - Create new verifier
- `with_key(self, key: Ed25519PublicKey) -> Self` - Set public key for verification
- `with_key_getter(self, getter: Box<dyn Fn(&str) -> Result<Ed25519PublicKey, KeyError>>) -> Self` - Set dynamic key lookup
- `with_tolerance(self, seconds: u64) -> Self` - Set timestamp tolerance (default: 60s)
- `verify_request(&self, request: &Request<Vec<u8>>, body: &[u8]) -> Result<VerificationResult, HttpSigError>` - Verify HTTP request
## Wire Format
When a request is signed, it includes these headers:
```http
POST /api/v1/payments HTTP/1.1
Host: api.example.com
Content-Type: application/json
Content-Digest: sha-256=:X48E9qHmRKpD1nxWnGjcKuXG7AEn9ELUE30CQSY9p3w=:
Signature-Input: sig1=("@method" "@path" "@authority" "content-digest");keyid="user-123";alg="ed25519";created=1735652986
Signature: sig1=:p7By5l82mNkP9qHmRKpD1nxWnGjcKuXG7AEn9ELUE30CQSY9p3w=:
{"amount": 100}
```
## Key Formats
All keys are represented as hex strings:
| Ed25519 Private Key | 32 | 64 chars |
| Ed25519 Public Key | 32 | 64 chars |
| X25519 Private Key | 32 | 64 chars |
| X25519 Public Key | 32 | 64 chars |
| Ed25519 Signature | 64 | 128 chars |
## Security Properties
### Signing (Ed25519)
- **Deterministic**: Same message + key = same signature
- **Unforgeable**: Cannot create valid signature without the private key
- **Verifiable**: Anyone with public key can verify authenticity
### Encryption (X25519 + ChaCha20-Poly1305)
- **Confidential**: Only recipient's private key can decrypt
- **Authenticated**: Tampering is detected (AEAD)
- **Forward Secure**: Each message uses ephemeral keys
### Symmetric (XChaCha20-Poly1305)
- **Authenticated**: Tampering detection via Poly1305 MAC
- **Large Nonce**: 192-bit nonces prevent collisions
- **Memory Safe**: Keys are automatically zeroized on drop
## Security Considerations
### Ed25519 Keys
1. **Private Key Storage**: Never store private keys in plaintext. Use secure key management.
2. **Random Generation**: Uses OS entropy via `rand::thread_rng()` for cryptographically secure randomness.
3. **Constant-Time Operations**: `secure_compare` prevents timing attacks.
4. **WASM Compatibility**: Uses `getrandom` with `wasm_js` feature for browser support.
### HTTP Signatures
- ✅ **Authenticity**: Only the holder of the private key can create valid signatures
- ✅ **Integrity**: Headers and body are cryptographically bound to the signature
- ✅ **Replay Protection**: Configurable timestamp tolerance (default: 60s)
- ✅ **Mandatory Digest**: All requests include Content-Digest, even GET/DELETE
- ✅ **Canonical Authority**: Normalized host:port prevents proxy attacks
## RFC Compliance
- **RFC 9421**: HTTP Message Signatures - Full compliance
- **RFC 9530**: Digest Fields - Content-Digest implementation
- **Ed25519**: Fast, secure elliptic curve signatures
## Testing
```bash
# From workspace root (runs all Rust unit tests AND Rhai integration tests)
cargo test -p herolib-crypt --features full
# Run Rhai examples
cargo run --features full --example run_rhai
# Run specific Rhai examples manually
cargo run --features full --example run_rhai -- packages/crypt/examples_rhai/keys/basic_signing.rhai
cargo run --features full --example run_rhai -- packages/crypt/examples_rhai/httpsig/basic_usage.rhai
# Run Rhai tests
cargo run --features full --example run_rhai -- packages/crypt/rhai_tests/keys/run_all_tests.rhai
cargo run --features full --example run_rhai -- packages/crypt/rhai_tests/httpsig/run_all_tests.rhai
```
Test coverage:
- **119 unit tests** - Core functionality
- **33 doc tests** - Documentation examples
- **Rhai examples** - Keys and HTTP signatures
- **Rhai tests** - Comprehensive test suites for keys and httpsig
## Architecture
The package is organized with clear module boundaries:
- **Feature Flags**: Compile only what you need (httpsig, rhai)
- **Clean Modules**: Clear boundaries between asymmetric, symmetric, keys, and httpsig
- **Unified API**: Single entry point for all cryptographic operations
- **Mandatory Keys**: Core Ed25519 functionality always available
Benefits:
- ✅ Simplified dependency management
- ✅ Selective compilation via features
- ✅ Unified Rhai integration
- ✅ Comprehensive cryptographic coverage
## Dependencies
- `ed25519-dalek` - Ed25519 signatures
- `x25519-dalek` - X25519 key exchange
- `chacha20poly1305` - AEAD encryption
- `sha3` - Key derivation (KDF)
- `sha2` - SHA-256 hashing
- `argon2` - Password-based key derivation
- `zeroize` - Secure memory handling
- `http` (optional) - HTTP types for httpsig
- `rhai` (optional) - Scripting support
## Specifications
See the detailed specifications:
- [SPEC_SIGNING.md](src/asymmetric/SPEC_SIGNING.md) - Signing specification
- [SPEC_ENCRYPTION.md](src/asymmetric/SPEC_ENCRYPTION.md) - Encryption specification
## License
Apache-2.0