rapidclock
rapidclock

Reputation: 1717

Encrypt/Decrypt Large text using rust's openssl library

I am trying to write an API to use rust's openssl wrapper library to encrypt/decrypt files.

This is the library : https://docs.rs/openssl/0.10.25/openssl/index.html

All the examples in the documentation, show how to encrypt short strings using Padding. I want to encrypt possibly long files.

The problem is I can only encrypt up to the length of the key(using Padding::NONE). I want to possibly encrypt longer pieces of data.

So in order to get around that problem, I break the data into chunks, encrypt/decrypt each chunk, append the encrypted/decrypted chunk into a result Vector. I then write it to file & read from file to vector before decrypting the same.

Outline of what I do:

  1. Load/Generate public/private keys.
  2. Load the file to encrypt into a Vec<u8> (or string slice, ultimately)
  3. Use public key to encrypt the data
  4. Write this encrypted data to a file
  5. Load the file with encrypted data to a Vec<u8>
  6. Decrypt using private key
  7. Write decrypted data to another file.
  8. Compare original to decrypted data.

Code is here (Beware - I copy-pasted stuff to one block below, originally they were in separate modules/files) I generate the rsa key initially like

fn generate_rsa_key_pair() -> utils::Pair<Vec<u8>, Vec<u8>> {
    let rsa = Rsa::generate(4096).unwrap();

    let public_pem = rsa.public_key_to_pem().unwrap();
    let private_pem = rsa.private_key_to_pem().unwrap();

    Pair::new(public_pem, private_pem) // custom data structure (basically a tuple)
}

I then write them to a file and use the keys from the file for subsequent runs.

extern crate openssl;

use openssl::pkey::{Private, Public};
use openssl::rsa::{Padding, Rsa};
use std::cmp::max;
use std::io::{BufWriter, Result, Write};
use std::str;

fn main() {
    let (public, private) = generate_rsa_from_files();
    let data = read_file_into_binary_vec("./resources/example_text.txt").unwrap();
    let enc_data = encrypt_data_with_pubkey(data.as_slice(), public).unwrap();
    println!("{:?}", enc_data);
    binary_slice_to_file(enc_data.as_slice(), "./resources/example_enc.txt").unwrap();
    let dec_data = decrypt_data_with_prikey(enc_data.as_slice(), private).unwrap();
    println!("{}", str::from_utf8(dec_data.as_slice()).unwrap());
    binary_slice_to_file(dec_data.as_slice(), "./resources/example_dec.txt").unwrap();
}

fn generate_rsa_from_files() -> (Vec<u8>, Vec<u8>) {
    let public = read_file_into_binary_vec("./resources/public_key").unwrap();
    let private = read_file_into_binary_vec("./resources/private_key").unwrap();
    (public, private)
}

fn read_file_into_binary_vec(file_path: &str) -> Result<Vec<u8>> {
    std::fs::read(file_path)
}

fn binary_slice_to_file(data: &[u8], file_path: &str) -> Result<()> {
    let file = std::fs::File::create(file_path)?;
    let mut buf_writer = BufWriter::new(file);
    buf_writer.write_all(data)
}

fn encrypt_data_with_pubkey(data: &[u8], pub_key: Vec<u8>) -> Result<Vec<u8>> {
    let data_len = data.len();
    let public_rsa: Rsa<Public> = Rsa::public_key_from_pem(pub_key.as_slice())?;
    let buf_len = public_rsa.size() as usize;
    let mut buffer: Vec<u8> = vec![0; buf_len];
    let mut encrypted_data: Vec<u8> = Vec::with_capacity(max(data_len, buf_len));
    println!("{}", public_rsa.size());
    for chunk in data.chunks(buf_len) {
        println!("Encrypting (len = {}): {:?}", chunk.len(), chunk);
        let chunk_mod;
        if chunk.len() < buf_len {
            chunk_mod = pad_chunk_to_size(chunk, buf_len);
        } else {
            chunk_mod = Vec::from(chunk);
        }
        let chunk_mod = chunk_mod.as_slice();
        println!("Encrypting (len = {}): {:?}", chunk_mod.len(), chunk_mod);
        let enc_len = public_rsa
            .public_encrypt(chunk_mod, buffer.as_mut_slice(), Padding::NONE)
            .expect("Error Encrypting");
        println!("Enc Data Len : {}", enc_len);
        encrypted_data.extend_from_slice(buffer.as_slice());
    }
    Ok(encrypted_data)
}

fn decrypt_data_with_prikey(enc_data: &[u8], priv_key: Vec<u8>) -> Result<Vec<u8>> {
    let data_len = enc_data.len();
    let private_rsa: Rsa<Private> = Rsa::private_key_from_pem(priv_key.as_slice())?;
    let buf_len = private_rsa.size() as usize;
    let mut buffer: Vec<u8> = vec![0; buf_len];
    let mut decrypted_data: Vec<u8> = vec![0; data_len];
    println!("{}", private_rsa.size());
    for chunk in enc_data.chunks(buf_len) {
        private_rsa.private_decrypt(chunk, &mut buffer, Padding::NONE).expect("Error Decrypting");;
        decrypted_data.extend_from_slice(buffer.as_slice());
    }
    Ok(decrypted_data)
}

fn pad_chunk_to_size(chunk: &[u8], desired_size: usize) -> Vec<u8> {
    let mut resized_vec = Vec::with_capacity(desired_size);
    for &element in chunk {
        resized_vec.push(element);
    }
    while resized_vec.len() < desired_size {
        resized_vec.push(0);
    }
    println!(
        "Desired Length = {}, Actual Length = {}",
        desired_size,
        resized_vec.len()
    );
    resized_vec
}

The problem I face is, In my file, I find so many weird characters at the beginning and end of the decrypted file. Image from vim shown below.

enter image description here

I cannot seem to figure out how to get rid of those characters(which ultimately increase my file size) and, if I chose to use padding, how I would go about doing the same.

Edit: I should note, if I print the output to terminal, I cannot see those characters, but they are definitely present in the file.

Any help is appreciated.

Upvotes: 0

Views: 3606

Answers (1)

Jmb
Jmb

Reputation: 23329

Those characters are NUL bytes. They come from two areas in your code:

  • Those at the beginning come from your decrypt_data_with_prikey function. That function:
    • Initializes its output vector with data_len zeroes;
    • Then appends the decrypted data to this vector. The initial zeroes are never removed nor overwritten. Replace let mut decrypted_data: Vec<u8> = vec![0; data_len] with let mut decrypted_data = Vec::new() to start with an empty vector or let mut decrypted_data = Vec::with_capacity (data_len) if you want to pre-allocate the memory for the data.
  • Those at the end come from the padding that you add during encryption. You will need to somehow convey the amount of padding that was added from the encryption function to the decryption function so that you can truncate the decrypted data to the appropriate length.

As a somewhat unrelated note, when encrypting data with a public/private key pair, it is usually recommended to:

  • Create a random symmetric key;
  • Encrypt that symmetric key with the public key;
  • Encrypt the data with the symmetric key;
  • Add the encrypted symmetric key in a header so that the receiver can know it.

This approach has two advantages:

  • Increased speed since symmetric cyphers are much faster than asymmetric.
  • Increased security because it leaks less information about the asymmetric key pair, so even if an attacker manages to decrypt a message, she will not be able to decrypt other messages that use the same key pair.

Upvotes: 2

Related Questions