neosmart / cryptostream

Read and Write stream adapters for on-the-fly encryption and decryption for rust
https://neosmart.net/blog/2018/transparent-encryption-and-decryption-in-rust-with-cryptostreams/
Other
23 stars 6 forks source link

Decryption is slow for big files and strange behaviour with some buffer size. #9

Closed nicolaspernoud closed 2 years ago

nicolaspernoud commented 2 years ago

Hello, The following code, used with a quite big file (300 Mo) :

use std::fs;
use std::fs::File;
use std::io;
use std::io::Read;
use std::io::Write;

use base64::decode;
use cryptostream::read;
use cryptostream::write;
use openssl::symm::Cipher;

fn main() -> io::Result<()> {
    let mut in_file = File::open("data/in.mp4")?;
    fs::remove_file("data/cipher.mp4");
    fs::remove_file("data/out.mp4");
    let cipher_file = File::create("data/cipher.mp4")?;
    let mut out_file = File::create("data/out.mp4")?;

    let mut buffer = [0u8; 4096]; // with 4096, out file is not identical to in file, but it is okay with 2048 (!?)

    let key: Vec<_> = decode("kjtbxCPw3XPFThb3mKmzfg==").unwrap();
    let iv: Vec<_> = decode("dB0Ej+7zWZWTS5JUCldWMg==").unwrap();

    let mut encryptor =
        write::Encryptor::new(cipher_file, Cipher::aes_128_cbc(), &key, &iv).unwrap();

    loop {
        let nbytes = in_file.read(&mut buffer).unwrap();
        encryptor.write(&buffer[..nbytes]).unwrap();
        if nbytes == 0 {
            break;
        }
    }

    encryptor.finish().unwrap();

    let cipher_file = File::open("data/cipher.mp4")?;

    let mut decryptor =
        read::Decryptor::new(cipher_file, Cipher::aes_128_cbc(), &key, &iv).unwrap();

    loop {
        let nbytes = match decryptor.read(&mut buffer) {
            Ok(nbytes) => nbytes,
            Err(e) => {
                println!("Error: {}", e);
                0
            }
        };
        out_file.write(&buffer[..nbytes]).unwrap();
        if nbytes == 0 {
            break;
        }
    }
    out_file.flush().unwrap();

    Ok(())
}
mqudsi commented 2 years ago

I'll have to look into the buffer size corruption issue because it seems like your code is fine at a first glance. However for the speed, note that cryptostream does not do any buffering (because it can return the underlying stream but doesn't necessarily consume everything it has read, so data would be lost otherwise) so you really should use it with a BufReader/BufWriter between it in and the source/destination. Can you see if that fixes your slowness issues?

mqudsi commented 2 years ago

I believe your issue with the decrypted content mismatch is caused by your use of .write(...) instead of .write_all(...) which is a common mistake with people make when using the rust Write trait (and not related to the cryptostream crate).

mqudsi commented 2 years ago

The slowness decrypting is not related to cryptostream or even how it reads from the source stream; it's the writes to the plain rust std::io::File (from the in-memory decrypted buffer) that introduce the biggest slowdown.

For a 100 MiB encrypted intermediate file, decrypting it with the following takes 13 seconds:

let enc_path = Path::new("encrypted.bin");
let dec_path = Path::new("decrypted.bin");

let encrypted = File::open(&enc_path).unwrap();
let mut decryptor = read::Decryptor::new(encrypted, Cipher::aes_128_cbc(), &key, &iv).unwrap();
let mut decrypted = File::create(&dec_path).unwrap();
let mut buffer = [0u8; 4096];

loop {
    let read = decryptor.read(&mut buffer).unwrap();
    decrypted.write_all(&buffer[..read]).unwrap();
    if read == 0 {
        break;
    }
}

decrypted.flush.unwrap();

While adding just one line brings down the decryption speed to 1.3s:

let enc_path = Path::new("encrypted.bin");
let dec_path = Path::new("decrypted.bin");

let encrypted = File::open(&enc_path).unwrap();
let mut decryptor = read::Decryptor::new(encrypted, Cipher::aes_128_cbc(), &key, &iv).unwrap();
let mut decrypted = File::create(&dec_path).unwrap();
let mut decrypted = BufWriter::new(decrypted); // THIS LINE WAS ADDED
let mut buffer = [0u8; 4096];

loop {
    let read = decryptor.read(&mut buffer).unwrap();
    decrypted.write_all(&buffer[..read]).unwrap();
    if read == 0 {
        break;
    }
}

decrypted.flush.unwrap();
nicolaspernoud commented 2 years ago

Hello,

Ok, thanks a lot for your answers. The buffread (in my use case) and the write_all did it !