Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/rustic-rs/rustic_core/llms.txt

Use this file to discover all available pages before exploring further.

The crypto module provides cryptographic operations for securing repository data. All blob data and metadata is encrypted using authenticated encryption (AEAD).

Overview

Rustic uses:
  • AES-256-CTR for encryption
  • Poly1305-AES for authentication (MAC)
  • SHA-256 for hashing
  • scrypt for key derivation from passwords
This combination provides:
  • Confidentiality (encryption)
  • Integrity (MAC)
  • Content-addressed storage (hashing)

Key Structure

The master key is 64 bytes, split into three parts:
use rustic_core::crypto::aespoly1305::Key;

pub struct Key {
    // [0..32]  - AES-256 encryption key
    // [32..48] - Poly1305 'k' value
    // [48..64] - Poly1305 'r' value
}

Creating Keys

// Generate random key
let key = Key::new();

// From components
let key = Key::from_keys(
    &encrypt_bytes, // 32 bytes
    &k_bytes,       // 16 bytes
    &r_bytes,       // 16 bytes  
);

// From slice
let key = Key::from_slice(&key_bytes); // 64 bytes

Encryption

Encryption adds a random nonce and authentication tag:
use rustic_core::crypto::CryptoKey;

let plaintext = b"secret data";
let encrypted = key.encrypt_data(plaintext)?;

// Format:
// [0..16]   - Random nonce
// [16..N]   - Encrypted data
// [N..N+16] - Poly1305 MAC tag
Encrypted data structure:
┌─────────────────┬─────────────────┬──────────────┐
│  Nonce (16B)    │  Ciphertext     │  Tag (16B)   │
│  Random IV      │  AES-CTR        │  Poly1305    │
└─────────────────┴─────────────────┴──────────────┘
Example:
use rustic_core::crypto::aespoly1305::Key;
use rustic_core::crypto::CryptoKey;

// Encrypt
let data = b"Hello, World!";
let encrypted = key.encrypt_data(data)?;

assert_eq!(encrypted.len(), 16 + data.len() + 16);

Decryption

Decryption verifies the MAC before returning plaintext:
// Decrypt and verify
let decrypted = key.decrypt_data(&encrypted)?;
assert_eq!(decrypted, plaintext);

// MAC verification failure
let corrupted = &mut encrypted.clone();
corrupted[20] ^= 0xFF; // Flip a bit

let result = key.decrypt_data(corrupted);
assert!(result.is_err()); // MAC check fails
Security: The MAC is checked before any data is returned, preventing tampering.

Hashing

SHA-256 hashing for content-addressed storage:
use rustic_core::crypto::hasher::hash;
use rustic_core::id::Id;

// Hash data to get blob ID
let data = b"file contents";
let id: Id = hash(data);

println!("Blob ID: {}", id); // 32-byte hash as hex

Streaming Hash

For large data:
use rustic_core::crypto::hasher::hash_reader;
use std::fs::File;

// Hash file without loading into memory
let file = File::open("large-file.bin")?;
let id = hash_reader(file)?;

Key Derivation

Password-based key derivation using scrypt:
use rustic_core::repofile::keyfile::KeyFile;

// Key file contains scrypt parameters
pub struct KeyFile {
    pub kdf: String,      // "scrypt"
    pub n: u32,           // CPU/memory cost (2^n)
    pub r: u32,           // Block size
    pub p: u32,           // Parallelization
    pub salt: Vec<u8>,    // Random salt
    pub data: Vec<u8>,    // Encrypted master key
}

Deriving Key from Password

let password = "my-secure-password";

// 1. Derive key from password using scrypt
let derived_key = key_file.kdf_key(&password)?;

// 2. Decrypt master key
let master_key = key_file.key_from_data(&derived_key)?;

// Or combined:
let master_key = key_file.key_from_password(&password)?;
scrypt parameters (recommended):
  • N = 2^15 = 32768 (log_n = 15)
  • r = 8
  • p = 1
  • 64-byte output

Generating Key Files

use rustic_core::repofile::keyfile::KeyFile;

let master_key = Key::new();
let password = "secure-password";

let key_file = KeyFile::generate(
    master_key,
    &password,
    Some("hostname".to_string()),
    Some("username".to_string()),
    true, // include creation time
)?;

// Save to backend
backend.save_file(&key_file)?;

MasterKey Structure

The master key is encrypted and stored:
pub struct MasterKey {
    pub mac: Mac,           // Poly1305 parameters
    pub encrypt: Vec<u8>,   // AES key
}

pub struct Mac {
    pub k: Vec<u8>,  // 16 bytes
    pub r: Vec<u8>,  // 16 bytes
}
Creation:
use rustic_core::repofile::keyfile::MasterKey;

let master_key = MasterKey::new(); // Random
let key = master_key.key();        // Extract Key

CryptoKey Trait

Generic interface for encryption/decryption:
pub trait CryptoKey {
    fn encrypt_data(&self, data: &[u8]) -> RusticResult<Vec<u8>>;
    fn decrypt_data(&self, data: &[u8]) -> RusticResult<Vec<u8>>;
}
Implemented by Key for AES-256-CTR + Poly1305-AES.

Security Properties

Authenticated Encryption (AEAD)

  • Confidentiality: AES-256 in CTR mode
  • Authenticity: Poly1305-AES MAC
  • Random IVs: Each encryption uses fresh nonce

Content Addressing

  • SHA-256 hashing: 256-bit security
  • Collision resistance: Deduplication is safe
  • Tamper detection: Changed data = different ID

Key Derivation

  • scrypt: Memory-hard, GPU-resistant
  • Random salt: 64 bytes per key file
  • Tunable cost: Increase N for more security

Common Patterns

Encrypting Repository Data

use rustic_core::crypto::CryptoKey;

// Serialize and encrypt
let json = serde_json::to_vec(&snapshot)?;
let encrypted = key.encrypt_data(&json)?;

// Write to backend
backend.write_bytes(FileType::Snapshot, &snapshot_id, encrypted)?;

Decrypting Repository Data

// Read encrypted data
let encrypted = backend.read_full(FileType::Snapshot, &snapshot_id)?;

// Decrypt and deserialize
let decrypted = key.decrypt_data(&encrypted)?;
let snapshot: SnapshotFile = serde_json::from_slice(&decrypted)?;

Content-Addressed Blobs

// Hash determines blob ID
let blob_id = hash(&data).into();

// Encrypt blob data
let encrypted = key.encrypt_data(&data)?;

// Store: ID is hash of plaintext, not ciphertext
pack.add_blob(blob_id, encrypted)?;

Error Handling

Crypto errors indicate serious problems:
match key.decrypt_data(&data) {
    Ok(plaintext) => Ok(plaintext),
    Err(e) if e.is_code("C001") => {
        // MAC verification failed - data corrupted or wrong key
        Err("Decryption failed: wrong password or corrupted data")
    }
    Err(e) => Err(e),
}

Performance

Encryption overhead:
  • 32 bytes per blob (nonce + tag)
  • ~10-50 MB/s on modern CPUs
Key derivation:
  • scrypt: ~100ms per password check
  • Intentionally slow (prevents brute force)
Hashing:
  • SHA-256: ~500 MB/s per core
  • Streaming for large files

See Also

Build docs developers (and LLMs) love