vault-core 0.1.1

Password based encryption/decryption
Documentation
extern crate core;

use std::error::Error;
use std::fs::File;
use std::io::{Read, Write};

use chacha20::XChaCha20;
use chacha20poly1305::aead::stream;
use chacha20poly1305::aead::stream::{Decryptor, Encryptor, StreamBE32};
use chacha20poly1305::consts::U24;
use chacha20poly1305::{ChaChaPoly1305, KeyInit, XChaCha20Poly1305};

use err::EncryptionError;

use crate::err::DecryptionError;

mod err;
mod keygen;

type Cypher = ChaChaPoly1305<XChaCha20, U24>;

const VERSION: u8 = 1;
const BUFFER_SIZE: usize = 1024;
const AUTHENTICATION_TAG_SIZE: usize = 16;

pub fn encrypt_file(
    source_path: &str,
    target_path: &str,
    password: &str,
) -> Result<(), Box<dyn Error>> {
    let mut generated_key = keygen::generate_key(password)?;
    let nonce = generated_key.nonce.as_slice();
    let salt = generated_key.salt.as_slice();

    let aead = XChaCha20Poly1305::new(generated_key.key[..32].as_ref().into());
    let encryptor: Encryptor<Cypher, StreamBE32<Cypher>> =
        stream::EncryptorBE32::from_aead(aead, nonce.into());

    let mut source_file = File::open(source_path)?;
    let mut destination_file = File::create(target_path)?;

    write_header(&mut destination_file, salt, nonce)?;

    write_encrypted_file(&mut source_file, &mut destination_file, encryptor)?;

    generated_key.cleanup();

    Ok(())
}

pub fn decrypt_file(
    source_path: &str,
    target_path: &str,
    password: &str,
) -> Result<(), Box<dyn Error>> {
    let mut version = [0u8; 1];
    let mut salt = [0u8; 32];
    let mut nonce = [0u8; 19];

    let mut source_file = File::open(source_path)?;
    let mut target_file = File::create(target_path)?;

    let mut bytes_read = source_file.read(&mut version)?;
    if bytes_read != version.len() {
        return Err(Box::new(DecryptionError::new(
            "Invalid file format".to_string(),
        )));
    }

    bytes_read = source_file.read(&mut salt)?;
    if bytes_read != salt.len() {
        return Err(Box::new(DecryptionError::new(
            "Invalid file format".to_string(),
        )));
    }

    bytes_read = source_file.read(&mut nonce)?;
    if bytes_read != nonce.len() {
        return Err(Box::new(DecryptionError::new(
            "Invalid file format".to_string(),
        )));
    }

    let mut generated_key = keygen::derive_key(password, salt.to_vec(), nonce.to_vec())?;

    let aead = XChaCha20Poly1305::new(generated_key.key[..32].as_ref().into());
    let decryptor: Decryptor<Cypher, StreamBE32<Cypher>> =
        stream::DecryptorBE32::from_aead(aead, generated_key.nonce.as_slice().into());

    write_decrypted_file(&mut source_file, &mut target_file, decryptor)?;

    generated_key.cleanup();

    Ok(())
}

fn write_decrypted_file(
    src: &mut File,
    target: &mut File,
    mut decryptor: Decryptor<Cypher, StreamBE32<Cypher>>,
) -> Result<(), Box<dyn Error>> {
    const DECRYPTION_BUFFER_SIZE: usize = BUFFER_SIZE + AUTHENTICATION_TAG_SIZE;
    let mut buffer = [0u8; DECRYPTION_BUFFER_SIZE];

    loop {
        let bytes_read = src.read(&mut buffer)?;
        let all_bytes_read = bytes_read < DECRYPTION_BUFFER_SIZE;
        let buffer_slice = &buffer[..bytes_read];

        if all_bytes_read {
            let plain_text = decryptor
                .decrypt_last(buffer_slice)
                .map_err(|e| DecryptionError::new(e.to_string()))?;
            target.write(&plain_text)?;
            return Ok(());
        } else {
            let plain_text = decryptor
                .decrypt_next(buffer_slice)
                .map_err(|e| DecryptionError::new(e.to_string()))?;
            target.write(&plain_text)?;
        }
    }
}

fn write_encrypted_file(
    src: &mut File,
    target: &mut File,
    mut encryptor: Encryptor<Cypher, StreamBE32<Cypher>>,
) -> Result<(), Box<dyn Error>> {
    let mut buffer = [0u8; BUFFER_SIZE];

    loop {
        let bytes_read = src.read(&mut buffer)?;
        let all_bytes_read = bytes_read < BUFFER_SIZE;
        let buffer_slice = &buffer[..bytes_read];

        if all_bytes_read {
            let cipher_text = encryptor
                .encrypt_last(buffer_slice)
                .map_err(|e| EncryptionError::new(e.to_string()))?;
            target.write(&cipher_text)?;
            return Ok(());
        } else {
            let cipher_text = encryptor
                .encrypt_next(buffer_slice)
                .map_err(|e| EncryptionError::new(e.to_string()))?;
            target.write(&cipher_text)?;
        }
    }
}

fn write_header(file: &mut File, salt: &[u8], nonce: &[u8]) -> Result<(), Box<dyn Error>> {
    let version_buffer: [u8; 1] = [VERSION];
    file.write(&version_buffer)?;

    file.write(salt)?;
    file.write(nonce)?;

    Ok(())
}