DEV Community

Cover image for Easy Encryption In Rust
Thomas Pegler
Thomas Pegler

Posted on

2

Easy Encryption In Rust

Let's keep this short

I had a whole section planned here for explaining XChaCha and why it's good and what it's good for but, realistically, I don't think many people want to know that and besides, I have another post about why standard ChaCha is good (albeit referencing Typescript) so if you desperately want to know, go read the first article in this series. Or for a better read, I can highly recommend a book called Serious Cryptography by Jean-Philippe Aumasson.

Show me the code!

Here's the overall code for encrypt & decrypt functions

cargo add orion && cargo add rand
Enter fullscreen mode Exit fullscreen mode

This will add the necessary crates to use the functions

// encryption.rs
use std::{error::Error};
use std::fmt::{Debug, Display, Formatter, Result as FmtResult};
use std::fs;
use std::io::{Read, Write};

use orion::hazardous::{
    aead::xchacha20poly1305::{seal, open, Nonce, SecretKey as XSecretKey},
    mac::poly1305::POLY1305_OUTSIZE,
    stream::xchacha20::XCHACHA_NONCESIZE
};
use orion::hazardous::stream::chacha20::CHACHA_KEYSIZE;
use orion::kdf::{derive_key, Password, Salt};
use orion::errors::UnknownCryptoError;
use rand::prelude::*;

#[derive(Debug)]
pub struct CryptoError;

impl Error for CryptoError {}

impl Display for CryptoError {
    fn fmt(&self, f: &mut Formatter) -> FmtResult {
        write!( f, "Crypto failure" )
    }
}

const NONCE_PLUS_AD_SIZE: usize = XCHACHA_NONCESIZE + 32;

/// Split encrypted cipher text into IV, AD and encrypted text
fn split_encrypted( cipher_text: &[u8] ) -> (Vec<u8>, Vec<u8>, Vec<u8>) {
    return (
        cipher_text[..XCHACHA_NONCESIZE].to_vec(),
        cipher_text[XCHACHA_NONCESIZE..NONCE_PLUS_AD_SIZE].to_vec(),
        cipher_text[NONCE_PLUS_AD_SIZE..].to_vec(),
    )
}

/// Fill passed array with cryptographically random data from ring crate
fn get_random( dest: &mut [u8]) {
    let mut rng = rand::thread_rng();
    rng.fill( dest );
}

fn nonce() -> Vec<u8> {
    let mut randoms: [u8; 24] = [0; 24];
    get_random( &mut randoms );
    return randoms.to_vec();
}

fn auth_tag() -> Vec<u8> {
    let mut randoms: [u8; 32] = [0; 32];
    get_random( &mut randoms );
    return randoms.to_vec();
}

fn create_key( password: String, nonce: Vec<u8> ) -> XSecretKey {
    let password = Password::from_slice(password.as_bytes()).unwrap();
    let salt = Salt::from_slice(nonce.as_slice()).unwrap();
    let kdf_key = derive_key(&password, &salt, 15, 1024, CHACHA_KEYSIZE as u32).unwrap();
    let key = XSecretKey::from_slice( kdf_key.unprotected_as_bytes() ).unwrap();
    return key;
}

pub fn encrypt( password: String, data: String ) -> Result<Vec<u8>, CryptoError> {
    let nonce = nonce();
    let key = create_key( password, nonce.clone() );
    let nonce = Nonce::from_slice( nonce.as_slice() ).unwrap();
    let ad = auth_tag();

    // Get the output length
    let output_len = match data.len().checked_add( XCHACHA_NONCESIZE + POLY1305_OUTSIZE + ad.len() ) {
        Some( min_output_len ) => min_output_len,
        None => panic!( "Plaintext is too long" ),
    };

    // Allocate a buffer for the output
    let mut output = vec![0u8; output_len];
    output[..XCHACHA_NONCESIZE].copy_from_slice(nonce.as_ref());
    output[XCHACHA_NONCESIZE..NONCE_PLUS_AD_SIZE].copy_from_slice( ad.as_ref() );
    seal(&key, &nonce, data.as_bytes(), Some( ad.clone().as_slice() ), &mut output[NONCE_PLUS_AD_SIZE..]).unwrap();
    let split = split_encrypted( output.as_slice() );
    Ok(output)
}

pub fn decrypt( password: String, cipher_text:String ) -> Result<Vec<u8>, CryptoError> {
    let ciphertext = hex::decode(cipher_text).unwrap();
    let key = create_key(password, ciphertext[..XCHACHA_NONCESIZE].to_vec());
    let split = split_encrypted( ciphertext.as_slice() );
    let nonce = Nonce::from_slice( split.0.as_slice() ).unwrap();
    let mut output = vec![0u8; split.2.len()];

    open(&key, &nonce, split.2.as_slice(), Some( split.1.as_slice() ), &mut output ).unwrap();
    Ok(output.to_vec())
}
Enter fullscreen mode Exit fullscreen mode

And then you can use it a little like this:

// main.rs
mod encryption;

fn encrypt_text(text: String)  -> String {
    let pass = env::var("RUST_PASS").unwrap_or(String::from("Super_Secret_Key_To_Protect_You__"));
    let encrypted = encryption::encrypt(pass, text);

    match encrypted {
        Ok(encrypted) => hex::encode(encrypted),
        Err(e) => {
            info!("{:?}", e);
            String::from("FAILED")
        },
    }
}

fn decrypt_text(text: String)  -> Vec<u8> {
    let pass = env::var( "RUST_PASS" ).unwrap_or(String::from("Super_Secret_Key_To_Protect_You__"));
    let decrypted = encryption::decrypt(pass, text);

    match decrypted {
        Ok(decrypted) => decrypted,
        Err(e) => {
            info!("{:?}", e);
            Vec::new()
        },
    }
}
Enter fullscreen mode Exit fullscreen mode

Is this perfect? Almost certainly not. I'm still learning Rust and trying to find out how best to use encryption crates was a challenge in itself. As always, check with others, learn from others and always try to improve your code whenever you can.


Header by Kaustav Sarkar on Unsplash

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs