Self Encryption

A file content self-encryptor that provides convergent encryption on file-based data. It produces a DataMap type and several chunks of encrypted data. Each chunk is up to 4MB in size and has an index and a name (SHA3-256 hash of the content), allowing chunks to be self-validating.

Installation

[dependencies]
self_encryption = "0.31.0"

Core Concepts

DataMap

Holds the information required to recover the content of the encrypted file, stored as a vector of ChunkInfo (list of file's chunk hashes). Only files larger than 3 bytes (3 * MIN_CHUNK_SIZE) can be self-encrypted.

Chunk Sizes

  • MIN_CHUNK_SIZE: 1 byte

  • MAX_CHUNK_SIZE: This is actually adjustable, but defaults to 1 MiB, we use 4 MiB on the Network (before compression)

  • MIN_ENCRYPTABLE_BYTES: 3 bytes

Streaming File Encryption

use self_encryption::{streaming_encrypt_from_file, ChunkStore};
use std::path::Path;

// Implement your chunk store
struct MyChunkStore {
    // Your storage implementation
}

impl ChunkStore for MyChunkStore {
    fn put(&mut self, name: &[u8], data: &[u8]) -> Result<(), Error> {
        // Store the chunk
    }

    fn get(&self, name: &[u8]) -> Result<Vec<u8>, Error> {
        // Retrieve the chunk
    }
}

// Create chunk store instance
let store = MyChunkStore::new();

// Encrypt file using streaming
let file_path = Path::new("my_file.txt");
let data_map = streaming_encrypt_from_file(file_path, store).await?;

Streaming File Decryption

use self_encryption::streaming_decrypt_from_storage;
use std::path::Path;

// Decrypt to file using streaming
let output_path = Path::new("decrypted_file.txt");
streaming_decrypt_from_storage(&data_map, store, output_path).await?;

In-Memory Operations (Small Files)

Basic Encryption/Decryption

use self_encryption::{encrypt, decrypt};

// Encrypt bytes in memory
let data = b"Small data to encrypt";
let (data_map, encrypted_chunks) = encrypt(data)?;

// Decrypt using retrieval function
let decrypted = decrypt(
    &data_map,
    |name| {
        // Retrieve chunk by name from your storage
        Ok(chunk_data)
    }
)?;

Chunk Store Implementations

In-Memory Store

use std::collections::HashMap;

struct MemoryStore {
    chunks: HashMap<Vec<u8>, Vec<u8>>,
}

impl ChunkStore for MemoryStore {
    fn put(&mut self, name: &[u8], data: &[u8]) -> Result<(), Error> {
        self.chunks.insert(name.to_vec(), data.to_vec());
        Ok(())
    }

    fn get(&self, name: &[u8]) -> Result<Vec<u8>, Error> {
        self.chunks.get(name)
            .cloned()
            .ok_or(Error::NoSuchChunk)
    }
}

Disk-Based Store

use std::path::PathBuf;
use std::fs;

struct DiskStore {
    root_dir: PathBuf,
}

impl ChunkStore for DiskStore {
    fn put(&mut self, name: &[u8], data: &[u8]) -> Result<(), Error> {
        let path = self.root_dir.join(hex::encode(name));
        fs::write(path, data)?;
        Ok(())
    }

    fn get(&self, name: &[u8]) -> Result<Vec<u8>, Error> {
        let path = self.root_dir.join(hex::encode(name));
        fs::read(path).map_err(|_| Error::NoSuchChunk)
    }
}

impl DiskStore {
    fn new<P: Into<PathBuf>>(root: P) -> Self {
        let root_dir = root.into();
        fs::create_dir_all(&root_dir).expect("Failed to create store directory");
        Self { root_dir }
    }
}

Error Handling

The library provides an Error enum for handling various error cases:

pub enum Error {
    NoSuchChunk,
    ChunkTooSmall,
    ChunkTooLarge,
    InvalidChunkSize,
    Io(std::io::Error),
    Serialisation(Box<bincode::ErrorKind>),
    Compression(std::io::Error),
    // ... other variants
}

Best Practices

  1. Use streaming operations (streaming_encrypt_from_file and streaming_decrypt_from_storage) for large files

  2. Use basic encrypt/decrypt functions for small in-memory data

  3. Implement proper error handling for chunk store operations

  4. Verify chunks using their content hash when retrieving

  5. Use parallel operations when available for better performance

Last updated