herolib-crypt

Crates.ioherolib-crypt
lib.rsherolib-crypt
version0.3.13
created_at2025-12-28 20:48:37.017369+00
updated_at2026-01-24 05:24:12.86242+00
descriptionSimple and secure asymmetric cryptography: signing and encryption using Ed25519
homepage
repositoryhttps://github.com/herolib/herolib_rust
max_upload_size
id2009365
size314,063
kristof de spiegeleer (despiegk)

documentation

README

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:

[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

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

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

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)

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

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

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

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

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

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

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:

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

// 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:

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:

Key Type Size (bytes) Hex Length
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

# 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:

License

Apache-2.0

Commit count: 0

cargo fmt