Day 21 - rust-crypto

Relevancy: 1.9 stable

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.

day21.rs

extern crate crypto;

day21.rs

use crypto::digest::Digest;

day21.rs

use crypto::sha2::Sha256;

day21.rs

let input = "Hello world!";
    let mut sha = Sha256::new();
    sha.input_str(input);
    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:

day21.rs

extern crate rustc_serialize;

day21.rs

use crypto::symmetriccipher::SynchronousStreamCipher;

day21.rs

use std::iter::repeat;

day21.rs

let mut bytes: Vec<u8> = repeat(0u8).take(sha.output_bytes()).collect();
    sha.result(&mut bytes[..]);
    println!("{}", bytes.to_base64(STANDARD));

Here's the output:

$ cargo run
c0535e4be2b79ffd93291305436bf889314e4a3faec05ecffcbb7df31ad9e51a
wFNeS+K3n/2TKRMFQ2v4iTFOSj+uwF7P/Lt98xrZ5Ro=

Ciphers

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:

day21.rs

use crypto::aes::{self, KeySize};

day21.rs

use crypto::symmetriccipher::SynchronousStreamCipher;

day21.rs

use rand::{OsRng, Rng};

day21.rs

let mut gen = OsRng::new().expect("Failed to get OS random generator");
    let mut key: Vec<u8> = repeat(0u8).take(16).collect();
    gen.fill_bytes(&mut key[..]);
    let mut nonce: Vec<u8> = repeat(0u8).take(16).collect();
    gen.fill_bytes(&mut nonce[..]);
    println!("Key: {}", key.to_base64(STANDARD));
    println!("Nonce: {}", nonce.to_base64(STANDARD));
    let mut cipher = aes::ctr(KeySize::KeySize128, &key, &nonce);
    let secret = "I like Nickelback";
    let mut output: Vec<u8> = repeat(0u8).take(secret.len()).collect();
    cipher.process(secret.as_bytes(), &mut output[..]);
    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.

Here's the output:

$ 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'

HMAC

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

day21.rs

use crypto::hmac::Hmac;
use crypto::mac::Mac;

day21.rs

use rustc_serialize::hex::ToHex;

day21.rs

let mut hmac_key: Vec<u8> = repeat(0u8).take(32).collect();
    gen.fill_bytes(&mut hmac_key);
    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);
    hmac.input(message.as_bytes());
    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
True

See also