24 days of Rust - rust-crypto

Important note: this article is outdated! Go to http://zsiciarz.github.io/24daysofrust/ for a recent version of all of 24 days of Rust articles. The blogpost here is kept as it is for historical reasons.

The rust-crypto crate is a collection of a lot of cryptography primitives and algorithms. There are tools for calculating hashes, verifying data integrity, encryption etc. One disclaimer - it hasn't had a proper security audit yet and although the algorithms are well known and researched, the library itself might have security bugs. But which one doesn't?

Cryptographic hashes

Let's start with a simple task of computing a cryptographic hash of some value. We'll use the SHA-256 algorithm to demonstrate.

extern crate crypto;

use crypto::digest::Digest;
use crypto::sha2::Sha256;

fn main() {
    let input = "Hello world!";
    let mut sha = Sha256::new();
    println!("{}", sha.result_str());

All hash algorithms in rust-crypto implement the Digest trait, which defines the low level methods such as input() and result() operating on bytes, but also convenience string-based methods as shown above. We can use those low level methods for example to represent our hash as a base64-encoded string:

extern crate "rustc-serialize" as serialize;

use serialize::base64::{STANDARD, ToBase64};

let mut bytes = Vec::from_elem(sha.output_bytes(), 0u8);
println!("{}", bytes.to_base64(STANDARD));

Here's the output:

$ cargo run


To actually encrypt some data in a way that we can decrypt it back later, we need a cipher. A few of these are provided by the rust-crypto crate, here's an example of AES encryption in CTR mode:

use crypto::aes::{mod, KeySize};
use crypto::symmetriccipher::SynchronousStreamCipher;

use std::rand::{OsRng, Rng};

let mut gen = OsRng::new().ok().expect("Failed to get OS random generator");
let mut key = Vec::from_elem(16, 0u8);
let mut nonce = Vec::from_elem(16, 0u8);
println!("Key: {}", key.to_base64(STANDARD));
println!("Nonce: {}", nonce.to_base64(STANDARD));
let mut cipher = aes::ctr(KeySize::KeySize128, key.as_slice(), nonce.as_slice());
let secret = "I like Nickelback";
let mut output = Vec::from_elem(secret.len(), 0u8);
cipher.process(secret.as_bytes(), output.as_mut_slice());
println!("Ciphertext: {}", output.to_base64(STANDARD));

We generate the secret key and a nonce value with a secure random generator - OsRng. Then we need to call the ctr() function which returns a best possible implementation of AES-CTR cipher (taking into consideration CPU architecture etc.). What we get back is a trait object - a SynchronousStreamCipher value in a Box. Calling process() should encrypt our secret message and store the result bytes in the output vector. Unfortunately this is where I failed - if I uncomment the process call, my program panics with thread '<main>' has overflowed its stack message. Maybe I'm doing something wrong - feel free to call me out on that - or maybe I hit some bug in rust-crypto. I'll definitely try to fix it later, but now let's jump to the last topic for today.

Update: turns out it was an actual bug in rust-crypto. Here's the output of a working program:

$ cargo run
Key: NvDy+u51EfMC+amJzoJO+w==
Nonce: d5+SLyfPGUSeug50nK1WGA==
Ciphertext: vTVxjyUms4Z4jex/OcMcQlY=

We can decrypt this with Python (who said it would be Rust all the way?):

import base64
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend

key = base64.decodebytes(b'NvDy+u51EfMC+amJzoJO+w==')
nonce = base64.decodebytes(b'd5+SLyfPGUSeug50nK1WGA==')
ct = base64.decodebytes(b'vTVxjyUms4Z4jex/OcMcQlY=')
backend = default_backend()
cipher = Cipher(algorithms.AES(key), modes.CTR(nonce), backend=backend)
decryptor = cipher.decryptor()
print(decryptor.update(ct) + decryptor.finalize())
$ python3 decrypt.py
b'I like Nickelback'


Message authentication algorithms such as HMAC verify both data integrity and authenticity. Let's see how to generate a MAC for a given message:

use crypto::hmac::Hmac;
use crypto::mac::Mac;
use serialize::hex::ToHex;

let mut hmac_key = Vec::from_elem(32, 0u8);
let message = "Ceterum censeo Carthaginem esse delendam";
println!("Message: {}", message);
println!("HMAC key: {}", hmac_key.to_base64(STANDARD));
let mut hmac = Hmac::new(Sha256::new(), hmac_key.as_slice());
println!("HMAC digest: {}", hmac.result().code().to_hex());

As with the AES cipher example, we generate a random secret key. Then we create the Hmac value, passing a cryptographic hash function object (anything that implements Digest). Since Hmac doesn't have these convenience methods for working with strings, we manually feed the bytes and encode the digest to hexadecimal string. The program outputs a key (we should give it to the recipient of the message through some other secure channel) and an HMAC digest.

$ cargo run
Message: Ceterum censeo Carthaginem esse delendam
HMAC key: esU5jdGCbM7E/ME5WBECJ+BdX3kt7bcQ3HkeEK+W6ZQ=
HMAC digest: b3240371a17e1e9755b89b23449f0d85c4c361e94e081c7adbe5a89c2d901aaa

And here's a simple Python program to verify validity of the message using the hmac module:

import base64
import hashlib
import hmac

key = base64.decodebytes(b'esU5jdGCbM7E/ME5WBECJ+BdX3kt7bcQ3HkeEK+W6ZQ=')
message = b'Ceterum censeo Carthaginem esse delendam'
expected = 'b3240371a17e1e9755b89b23449f0d85c4c361e94e081c7adbe5a89c2d901aaa'
h = hmac.new(key, message, hashlib.sha256)
print(hmac.compare_digest(h.hexdigest(), expected))

So... is the message valid and authentic?

$ python3 verify.py

See also

Code examples in this article were built with rustc 0.13.0-nightly.

Photo by Garrett Coakley and shared under the Creative Commons Attribution-NonCommercial 2.0 Generic License. See https://www.flickr.com/photos/garrettc/3747802654