Transparent encryption and decryption in rust with cryptostreams

C# developers have long been spoiled when it comes to quickly and easily getting up and running with encryption thanks to the .NET CryptoStream  class, which wraps a Stream instance in a second Stream that automatically encrypts/decrypts anything read/written to/from it before passing it along to the underlying Stream. Long story short, it makes it ridiculously easy to add encryption or decryption facilities to an existing pipeline, as after setting up the CryptoStream instance you can just treat it like any other Stream object and read or write to it normally.

Encryption has been somewhat of a sore spot in the rust ecosystem after a few false starts with “native” rust encryption libraries that went nowhere, but today the rust community has fortunately adopted the OpenSSL bindings as the approach of choice, and the rust-openssl crate makes it easy to both bundle and consume the openssl bindings from rust in a cross-platform manner. What it doesn’t do is make encryption and decryption any easier than OpenSSL itself does.

Enter the cryptostream crate. Released on github and on crates.io under the MIT public license, cryptostream finally provides an easy and transparent way to add encryption and decryption to pipelines involving objects implementing Read or Write, making encryption (or decryption) as easy as creating a new cryptostream object, passing in an existing Read/Write impl, and then reading/writing from/to the cryptostream instead.

Here’s an example that highlights just how easy it makes the process:

let src: Vec =
	decode("vuU+0SXFWQLu8vl/o1WzmPCmf7x/O6ToGQ162Aq2CHxcnc/ax/Q8nTbRlNn0OSPrFuE3yDdOVC35RmwtUIlxKIkWbnxJpRF5yRJvVByQgWX1qLW8DfMjRp7gVaFNv4qr7G65M6hbSx6hGJXvQ6s1GiFwi91q0V17DI79yVrINHCXdBnUOqeLGfJ05Edu+39EQNYn4dky7VdgTP2VYZE7Vw==").unwrap();
let key: Vec<_> = decode("kjtbxCPw3XPFThb3mKmzfg==").unwrap();
let iv: Vec<_> = decode("dB0Ej+7zWZWTS5JUCldWMg==").unwrap();

// The source can be any object implementing `Read`. 
// In this case, a simple &[u8] slice.
let mut decryptor = read::Decryptor::new(src.as_slice(),
										 Cipher::aes_128_cbc(),
										 &key, &iv).unwrap();
// `decryptor` implements `Read`

let mut decrypted = [0u8; 1024]; // a buffer to decrypt into
let mut bytes_decrypted = 0;

loop {
	// Just read from the `Decryptor` as if it were
	// any other `Read` impl. Decryption is automatic.
	let read_count = decryptor.read(&mut decrypted[bytes_decrypted..])
                                  .unwrap();
	bytes_decrypted += read_count;
	if read_count == 0 {
		break;
	}
}

println!("{}", String::from_utf8_lossy(&decrypted));

Cryptography just doesn’t get any easier than this.

As rust doesn’t offer a native Stream type and instead relies on the Read and Write traits to provide stream-like functionality, cryptostream is split into two types, one class acting as a Read adapter (to “pull” encrypted or decrypted bytes from an existing Read resource), and the other as a Write adapter (to “push” plaintext into an encrypted file or ciphertext into a plaintext file). Furthermore, each of these cryptostream types is implemented as both a Encryptor and Decryptor class, so that it is possible to perform any [read/write] operation involving on-the-fly [encryption|decryption] of an existing [Read|Write] resource.

This design is inspired by the excellent flate2 crate, which provides similar on-the-fly transformation of Read/Write objects for purposes involving compression and decompression.

So all you have to do is choose one of the four cryptostream variants:

  • cryptostream::read::Encryptor
  • cryptostream::read::Decryptor
  • cryptostream::write::Encryptor
  • cryptostream::write::Decryptor

When you’ve chosen the right type, just initialize it with cryptostream::new(…), passing in the relevant IV, key, and underlying Read or Write resource you wish to transparently encrypt/decrypt to/from, and then read from or write to it as you would normally.

cryptostream does not yet support any AEAD cipher suites, but support for those will be coming. (If you’re using a cipher that doesn’t taken an IV, now would be a great time to rectify that design decision, but until then, you can pass in None for the IV and carry on your merry, insecure way.)

Feedback in the form of comments below or issues and pull requests on GitHub is most welcome.

3 thoughts on “Transparent encryption and decryption in rust with cryptostreams

  1. > Encryption has been somewhat of a sore spot in the rust ecosystem after a few false starts with “native” rust encryption libraries that went nowhere.

    You might not be aware that rust-sodium ( https://docs.rs/rust_sodium/0.10.0/rust_sodium/ ) provides libsodium’s high-level, hard-to-misuse constructions with a safe Rust interface.

    In particular, these constructions provide /authenticated encryption/, whereas cryptostreams provides AES-CBC, a malleable mode of encryption (i.e. it cannot detect someone having modified the ciphertext, *and* an attacker can perform modifications that result in modifications of their choice on the plaintext).

    I would strongly advise against using cryptostreams as released.

Leave a Reply

Your email address will not be published. Required fields are marked *