dcrypt-symmetric 1.2.1

Symmetric encryption algorithms for the dcrypt library
Documentation

Symmetric Cryptography (symmetric)

The dcrypt-symmetric crate provides a high-level, secure, and easy-to-use API for common symmetric encryption algorithms within the dcrypt ecosystem. It is built upon the low-level cryptographic primitives in dcrypt-algorithms and integrates with dcrypt-api for a unified and robust error-handling system.

This crate is designed for both ease of use in common scenarios and the flexibility required for more complex applications, such as streaming large files.


## Features

  • Modern AEAD Ciphers: Provides implementations for industry-standard Authenticated Encryption with Associated Data (AEAD) ciphers:
    • ChaCha20-Poly1305: As specified in RFC 8439.
    • XChaCha20-Poly1305: An extended nonce variant, ideal for applications requiring a large number of random nonces.
    • AES-128-GCM & AES-256-GCM: NIST-approved standard for high-performance, authenticated encryption.
  • Secure Key Management:
    • Secure random key generation.
    • Key derivation from passwords using PBKDF2-HMAC-SHA256.
    • Keys are wrapped in types that implement Zeroize to securely clear them from memory on drop.
    • Safe serialization format for storing and transmitting keys.
  • Convenient Data Handling:
    • Ciphertext Packages: An easy-to-use format that bundles the nonce and ciphertext together, simplifying storage and transmission.
    • Base64-encoded string representation for packages and keys.
  • Streaming API:
    • Memory-efficient streaming encryption and decryption for large files or data streams.
    • Handles nonce management automatically and securely across data chunks.
  • Unified Error System:
    • Leverages the dcrypt-api error system for consistent and descriptive error handling across the entire dcrypt library stack.
  • no_std Compatibility: Core features are available in no_std environments.

## Installation

Add the crate to your Cargo.toml file:

[dependencies]
dcrypt-symmetric = "0.12.0-beta.1"

Or add it via the command line:

cargo add dcrypt-symmetric

## Usage Examples

### Basic Encryption & Decryption (AES-256-GCM)

This example shows a simple encrypt/decrypt cycle using Aes256Gcm.

use dcrypt::symmetric::{Aes256Gcm, Aes256Key, Aead, SymmetricCipher, Result};

fn main() -> Result<()> {
    // 1. Generate a new, random key for AES-256-GCM.
    let key = Aes256Key::generate();

    // 2. Create a new cipher instance.
    let cipher = Aes256Gcm::new(&key)?;

    // 3. The data to be encrypted.
    let plaintext = b"this is a very secret message";
    let associated_data = b"metadata"; // Optional associated data

    // 4. Generate a random nonce. It MUST be unique for each encryption with the same key.
    let nonce = Aes256Gcm::generate_nonce();

    // 5. Encrypt the data.
    println!("Encrypting: '{}'", String::from_utf8_lossy(plaintext));
    let ciphertext = cipher.encrypt(&nonce, plaintext, Some(associated_data))?;
    println!("Ciphertext (hex): {}", hex::encode(&ciphertext));


    // 6. Decrypt the data.
    let decrypted_plaintext = cipher.decrypt(&nonce, &ciphertext, Some(associated_data))?;
    println!("Decrypted: '{}'", String::from_utf8_lossy(&decrypted_plaintext));


    // 7. Verify the result.
    assert_eq!(plaintext, &decrypted_plaintext[..]);

    // Decryption will fail if the key, nonce, ciphertext, or AAD is incorrect.
    let wrong_key = Aes256Key::generate();
    let wrong_cipher = Aes256Gcm::new(&wrong_key)?;
    assert!(wrong_cipher.decrypt(&nonce, &ciphertext, Some(associated_data)).is_err());
    println!("Decryption with wrong key failed as expected.");

    Ok(())
}

### Packaged Encryption (ChaCha20Poly1305)

The library provides a convenient package format that bundles the nonce with the ciphertext, making it easy to store or transmit.

use dcrypt::symmetric::{
    ChaCha20Poly1305Cipher,
    ChaCha20Poly1305Key,
    ChaCha20Poly1305CiphertextPackage,
    SymmetricCipher,
    Result
};

fn main() -> Result<()> {
    // 1. Generate a key and create a cipher instance.
    let (cipher, key) = ChaCha20Poly1305Cipher::generate()?;

    let plaintext = b"data packaged for transport";

    // 2. Encrypt the data directly into a package.
    // This generates a random nonce internally and bundles it with the ciphertext.
    let package = cipher.encrypt_to_package(plaintext, None)?;

    // 3. The package can be serialized to a string for easy storage or transmission.
    let serialized_package = package.to_string();
    println!("Serialized Package: {}", serialized_package);

    // ... later, on another machine or after retrieving from storage ...

    // 4. Create a new cipher instance with the same key.
    let receiving_cipher = ChaCha20Poly1305Cipher::new(&key)?;

    // 5. Deserialize the package from the string.
    let parsed_package = ChaCha20Poly1305CiphertextPackage::from_string(&serialized_package)?;

    // 6. Decrypt from the package.
    let decrypted_plaintext = receiving_cipher.decrypt_package(&parsed_package, None)?;

    println!("Decrypted from package: '{}'", String::from_utf8_lossy(&decrypted_plaintext));
    assert_eq!(plaintext, &decrypted_plaintext[..]);

    Ok(())
}

### Key Derivation from a Password

Derive a strong cryptographic key from a user-provided password using PBKDF2.

use dcrypt::symmetric::{aes::derive_aes128_key, aes::generate_salt, Result};

fn main() -> Result<()> {
    let password = b"a-very-secure-password-123";
    
    // Generate a random salt. The salt should be stored alongside the encrypted data.
    let salt = generate_salt(16);

    // Set the number of iterations. Higher is more secure but slower.
    // OWASP recommends at least 100,000.
    let iterations = 250_000;

    // Derive a 128-bit key for AES.
    let derived_key = derive_aes128_key(password, &salt, iterations)?;
    
    println!("Successfully derived AES-128 key from password.");
    // This key can now be used to instantiate an Aes128Gcm cipher.

    Ok(())
}

### Streaming File Encryption

For large files, the streaming API encrypts and decrypts data in chunks, keeping memory usage low. This example uses std::io::Cursor to simulate file I/O.

use std::io::Cursor;
use dcrypt::symmetric::{
    streaming::chacha20poly1305::{ChaCha20Poly1305EncryptStream, ChaCha20Poly1305DecryptStream},
    streaming::{StreamingEncrypt, StreamingDecrypt},
    ChaCha20Poly1305Key,
    Result
};

fn main() -> Result<()> {
    // 1. Generate a key. In a real app, this would be loaded or derived.
    let key = ChaCha20Poly1305Key::generate();
    let associated_data = b"streaming-file-example";

    // 2. Simulate a large input file and an output buffer for encrypted data.
    let source_data = b"This is the first part of a very large file. ".repeat(1000);
    let mut source = Cursor::new(source_data);
    let mut encrypted_dest = Vec::new();

    // 3. Create an encryption stream.
    {
        let mut encrypt_stream = ChaCha20Poly1305EncryptStream::new(
            &mut encrypted_dest,
            &key,
            Some(associated_data),
        )?;
        
        // Pipe data from the source to the encryption stream.
        std::io::copy(&mut source, &mut encrypt_stream)?;
    } // The stream is finalized when it goes out of scope.

    println!("Original size: {} bytes", source.get_ref().len());
    println!("Encrypted size: {} bytes", encrypted_dest.len());

    // 4. Now, decrypt the data from the stream.
    let mut encrypted_source = Cursor::new(encrypted_dest);
    let mut decrypt_stream = ChaCha20Poly1305DecryptStream::new(
        &mut encrypted_source,
        &key,
        Some(associated_data),
    )?;

    // 5. Read the decrypted data back.
    let mut decrypted_data = Vec::new();
    decrypt_stream.read_to_end(&mut decrypted_data)?;
    
    println!("Decrypted size: {} bytes", decrypted_data.len());

    // 6. Verify the integrity of the data.
    assert_eq!(source.get_ref(), &decrypted_data);
    println!("Successfully encrypted and decrypted stream!");

    Ok(())
}


## License

This crate is licensed under the Apache 2.0 License.