Extensible RustCrypto
The RustCrypto crates share traits such as Digest, DynDigest and
Mac, so you can write code that works with any algorithm, or picks one at
runtime. These recipes cover jobs that are awkward with a C-style API, where
the algorithm is fixed by a function name; see
Choosing between ring and RustCrypto for when to use them. The simple
one-shot versions are on the Hashing page.
Choose a hash algorithm at runtime
sha2 data-encoding Cryptography
Use DynDigest when the algorithm name comes from a config file, a command
line flag or a protocol field and is not known at compile time; this is the
job of EVP_get_digestbyname in OpenSSL. Every hash in the RustCrypto family
can be boxed as a dyn DynDigest, so hasher_by_name returns one value type
for all of them. Unsupported names return None instead of a null pointer.
use data_encoding::HEXLOWER;
use sha2::digest::DynDigest;
use sha2::{Sha256, Sha384, Sha512};
/// Looks up a hash function by name, like `EVP_get_digestbyname` in OpenSSL.
fn hasher_by_name(name: &str) -> Option<Box<dyn DynDigest>> {
match name {
"sha256" => Some(Box::new(Sha256::default())),
"sha384" => Some(Box::new(Sha384::default())),
"sha512" => Some(Box::new(Sha512::default())),
_ => None,
}
}
fn main() {
// The algorithm names could just as well come from a config file.
for name in ["sha256", "sha384", "sha512", "md5"] {
match hasher_by_name(name) {
Some(mut hasher) => {
hasher.update(b"hello ");
hasher.update(b"world");
println!("{name}: {}", HEXLOWER.encode(&hasher.finalize()));
}
None => println!("{name}: unsupported algorithm"),
}
}
}
Hash data while writing it
sha2 data-encoding Cryptography
Use a wrapper around Write when you want the digest of a stream you are
already writing, such as a download or an archive, without reading it back.
HashingWriter works with any Digest, passes writes through to the inner
writer and hashes only the bytes it accepted, so partial writes stay correct.
Because it is a Write, it plugs into io::copy and anything else that
takes a writer.
use anyhow::Result;
use data_encoding::HEXLOWER;
use sha2::{Digest, Sha256};
use std::fs::File;
use std::io::{self, Write};
/// A writer that hashes everything written through it.
struct HashingWriter<W: Write, D: Digest> {
inner: W,
hasher: D,
}
impl<W: Write, D: Digest> HashingWriter<W, D> {
fn new(inner: W) -> Self {
Self {
inner,
hasher: D::new(),
}
}
fn finish(self) -> (W, Vec<u8>) {
(self.inner, self.hasher.finalize().to_vec())
}
}
impl<W: Write, D: Digest> Write for HashingWriter<W, D> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
// Only hash the bytes the inner writer actually accepted.
let written = self.inner.write(buf)?;
self.hasher.update(&buf[..written]);
Ok(written)
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
fn main() -> Result<()> {
let dir = tempfile::tempdir()?;
let path = dir.path().join("copy.txt");
// Copy a reader into a file and get its digest without reading it back.
let mut source = &b"We will generate a digest of this text"[..];
let mut writer = HashingWriter::<_, Sha256>::new(File::create(&path)?);
io::copy(&mut source, &mut writer)?;
let (_file, digest) = writer.finish();
println!(
"Wrote {} and its SHA-256 digest is {}",
path.display(),
HEXLOWER.encode(&digest)
);
Ok(())
}
Upgrade stored password hashes on login
Use this when you raise the cost of password hashing but cannot rehash users
who are not logged in. A PHC string records its own parameters, so
verify_and_upgrade can verify an old hash with the parameters stored in it,
read them back with Params::try_from and, if they are weaker than the
current ones, return a new hash to save. It returns None when the stored
hash is already current, and an error when the password is wrong.
use anyhow::Result;
use argon2::password_hash::phc::PasswordHash;
use argon2::password_hash::{PasswordHasher, PasswordVerifier};
use argon2::{Algorithm, Argon2, Params, Version};
/// The cost parameters new hashes should use.
fn current_argon2() -> Result<Argon2<'static>> {
let params = Params::new(32 * 1024, 3, 1, None)?;
Ok(Argon2::new(Algorithm::Argon2id, Version::V0x13, params))
}
/// Checks a login attempt. If the password is right but the stored hash was
/// made with weaker parameters, returns a replacement hash to store.
fn verify_and_upgrade(password: &str, stored: &str) -> Result<Option<String>> {
let parsed = PasswordHash::new(stored)?;
// The parameters in the stored hash are used to verify it.
Argon2::default().verify_password(password.as_bytes(), &parsed)?;
let current = current_argon2()?;
let used = Params::try_from(&parsed)?;
if used.m_cost() < 32 * 1024 || used.t_cost() < 3 {
let upgraded = current.hash_password(password.as_bytes())?.to_string();
return Ok(Some(upgraded));
}
Ok(None)
}
fn main() -> Result<()> {
let password = "Guess Me If You Can!";
// A hash from before the parameters were raised.
let weak = Params::new(8 * 1024, 1, 1, None)?;
let old_hash = Argon2::new(Algorithm::Argon2id, Version::V0x13, weak)
.hash_password(password.as_bytes())?
.to_string();
println!("Old hash: {old_hash}");
match verify_and_upgrade(password, &old_hash)? {
Some(new_hash) => println!("Upgraded: {new_hash}"),
None => println!("Hash already up to date"),
}
Ok(())
}
Write code that is generic over the MAC
Use the Mac trait when the algorithm should be chosen by the caller, for
example to migrate from HMAC-SHA256 to HMAC-SHA512. sign and verify take
the MAC as a type parameter, so there is one implementation for every
algorithm that implements Mac and KeyInit. Mac::verify_slice compares
in constant time. A tag only verifies with the algorithm that made it, which
the second assert! shows.
use hmac::{Hmac, KeyInit, Mac};
use sha2::{Sha256, Sha512};
fn sign<M: Mac + KeyInit>(
key: &[u8],
message: &[u8],
) -> Result<Vec<u8>, hmac::digest::InvalidLength> {
let mut mac = <M as KeyInit>::new_from_slice(key)?;
mac.update(message);
Ok(mac.finalize().into_bytes().to_vec())
}
/// Verifies a tag in constant time with whichever MAC `M` names.
fn verify<M: Mac + KeyInit>(key: &[u8], message: &[u8], tag: &[u8]) -> bool {
match <M as KeyInit>::new_from_slice(key) {
Ok(mut mac) => {
mac.update(message);
mac.verify_slice(tag).is_ok()
}
Err(_) => false,
}
}
fn main() -> Result<(), hmac::digest::InvalidLength> {
let key = b"my secret and secure key";
let message = b"input message";
let tag256 = sign::<Hmac<Sha256>>(key, message)?;
let tag512 = sign::<Hmac<Sha512>>(key, message)?;
println!("HMAC-SHA256 tag is {} bytes", tag256.len());
println!("HMAC-SHA512 tag is {} bytes", tag512.len());
assert!(verify::<Hmac<Sha256>>(key, message, &tag256));
// A tag only verifies with the algorithm that made it.
assert!(!verify::<Hmac<Sha512>>(key, message, &tag256));
Ok(())
}