HMAC-SHA256 and Rust

So how do we create an encryption key from a password? Well, the wrong answer is to hash a password, as this is insecure. The correct way…

Photo by Everyday basics on Unsplash

HMAC-SHA256 and Rust

So how do we create an encryption key from a password? Well, the wrong answer is to hash a password, as this is insecure. The correct way is to use a Key Derivation Function (KDF) and one of the most popular is HMAC Key Derivation function (HKDF). With this we derive our random key from a given hashing method such as SHA-1, SHA-256 and SHA-512. Into this we add a salt value and some extra information. In practice, though, we use HMAC-SHA256 not for password hashing, but for generating encryption keys based on a shared secret, such as within a Diffie-Hellman key exchange.

Initially, HKDF creates a pseudorandom key (PRK) using initial key material (IKM) and a salt value in order to produce an HMAC hash function (such as HMAC-SHA256). Next, the PRK output is used to produce a key of a required length — this is output is known as the OKM (output keying material). If we generate a 16-byte output (32 hex characters), we will have a 128-bit encryption key, and a 32-byte output (64 hex characters) gives us a 256-bit encryption key. HKDF has many applications such as in TLS 1.3 for generating encryption keys [RFC 5869][article].

Implementing with Rust

In this case we use the IKM as the input keying material and a salt value. From this, we will compute the PRK from the HMAC-Hash(salt, IKM) function and the OKM from the HKDF-Expand(PRK, info, L) function, and where L is the length of the key to be used, and info is additional information. An outline of the Rust code is [here]:

extern crate crypto;
extern crate sha1;
use rustc_serialize::hex::FromHex;
use std::iter::repeat;
// use crypto::sha1::Sha1;
use crypto::sha2::Sha256;
use std::env;

fn hex_to_bytes(s: &str) -> Vec<u8> {
s.from_hex().unwrap()
}
fn main() {
 //   let mut myikm="0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b";
let mut myikm="hello";
    let mut mysalt="000102030405060708090a0b0c";
let mut myinfo ="f0f1f2f3f4f5f6f7f8f9";

    let args: Vec<String> = env::args().collect();

if args.len() >1 { myikm = args[1].as_str();}
if args.len() >2 { mysalt = args[2].as_str();}
if args.len() >3 { myinfo = args[3].as_str();}

    let dig=Sha256::new();
    let salt=&hex_to_bytes( mysalt)[..];
let info=&hex_to_bytes( myinfo)[..];
// let ikm=&hex_to_bytes( myikm)[..];

let mut ikm=myikm.as_bytes();

    let mut prk: Vec = repeat(0).take(32).collect();
    crypto::hkdf::hkdf_extract(dig, &salt[..], &ikm[..], &mut prk);

    let mut okm: Vec = repeat(0).take(42).collect();
    crypto::hkdf::hkdf_expand(dig, &prk[..], &info[..], &mut okm);
    println!("== HMAC-SHA256 ==");
println!("Message: {}",myikm);
println!("Salt: {}",mysalt);
println!("Info: {}",myinfo);
println!("\nPRK (pseudo-random key): {}",hex::encode(prk));
println!("OKM (output keying material): {}",hex::encode(okm));
 //   Basic test case with SHA-256
 //   Hash = SHA-256
// IKM = 0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b (22 octets)
// salt = 0x000102030405060708090a0b0c (13 octets)

// PRK = 0x077709362c2e32df0ddc3f0dc47bba63
// 90b6c73bb50f9c3122ec844ad7c2b3e5 (32 octets)
// OKM = 0x3cb25f25faacd57a90434f64d0362f2a
// 2d2d0a90cf1a5a4c5db02d56ecc4c5bf
// 34007208d5b887185865 (42 octets)

    
}

Finally we simply build with:

cargo build

A sample run is [here]:

== HMAC-SHA256 ==
Message: hello
Salt: 0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b
Info: 000102030405060708090a0b0c
PRK (pseudo-random key): c196df69a5e50fc8a8304eb90033d8f234af75cea73a4e9b0777b8bf04540a44
OKM (output keying material): 22d46257730f68d51cd441b5c999d9bcb11e20a5eb82be0d93807cb5288eacc70dcdb7c141e329a455fc

And here is the running code: