Cryptography is fundamental to building secure systems. In this post, we'll explore how to work with cryptographic primitives in Rust using the ecosystem's excellent libraries.
Choosing the Right Crypto Library
The Rust ecosystem offers several high-quality cryptography crates:
ring: Fast, safe, and thoroughly auditedRustCrypto: Pure Rust implementations with extensive algorithm supportsodiumoxide: Rust bindings to libsodium
For this example, we'll use ring for its performance and security track record.
Hashing with BLAKE2
BLAKE2 is a cryptographic hash function faster than MD5, SHA-1, SHA-2, and SHA-3, yet is at least as secure as the latest standard SHA-3:
use ring::digest;
fn hash_data(data: &[u8]) -> Vec<u8> {
let digest = digest::digest(&digest::BLAKE2B_256, data);
digest.as_ref().to_vec()
}
fn main() {
let data = b"Hello, AnyVec!";
let hash = hash_data(data);
println!("Hash: {:?}", hash);
}Authenticated Encryption with ChaCha20-Poly1305
For encrypting data, always use authenticated encryption (AEAD):
use ring::aead;
use ring::rand::{SecureRandom, SystemRandom};
fn encrypt_message(key: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> {
let rng = SystemRandom::new();
// Generate a unique nonce
let mut nonce_bytes = vec![0u8; 12];
rng.fill(&mut nonce_bytes)?;
let unbound_key = aead::UnboundKey::new(
&aead::CHACHA20_POLY1305,
key
)?;
let sealing_key = aead::LessSafeKey::new(unbound_key);
let nonce = aead::Nonce::try_assume_unique_for_key(&nonce_bytes)?;
let mut ciphertext = plaintext.to_vec();
sealing_key.seal_in_place_append_tag(
nonce,
aead::Aad::empty(),
&mut ciphertext
)?;
// Prepend nonce to ciphertext
let mut result = nonce_bytes;
result.extend_from_slice(&ciphertext);
Ok(result)
}Key Derivation with PBKDF2
Never use passwords directly as encryption keys. Always use a key derivation function:
use ring::pbkdf2;
use std::num::NonZeroU32;
fn derive_key(password: &str, salt: &[u8]) -> [u8; 32] {
let iterations = NonZeroU32::new(100_000).unwrap();
let mut key = [0u8; 32];
pbkdf2::derive(
pbkdf2::PBKDF2_HMAC_SHA256,
iterations,
salt,
password.as_bytes(),
&mut key,
);
key
}Best Practices
1. Use High-Level APIs
Prefer high-level APIs that make it harder to misuse cryptography:
// Good: High-level API
let encrypted = encrypt_aead(key, plaintext)?;
// Avoid: Low-level primitives require expert knowledge
let cipher = Cipher::new(key);
let encrypted = cipher.encrypt_block(plaintext); // Easy to misuse!2. Generate Random Keys Properly
Always use cryptographically secure random number generators:
use ring::rand::{SecureRandom, SystemRandom};
let rng = SystemRandom::new();
let mut key = vec![0u8; 32];
rng.fill(&mut key)?; // Cryptographically secure3. Constant-Time Comparisons
Prevent timing attacks when comparing secrets:
use ring::constant_time;
fn verify_mac(expected: &[u8], actual: &[u8]) -> bool {
constant_time::verify_slices_are_equal(expected, actual).is_ok()
}Common Mistakes to Avoid
- Reusing nonces with the same key
- Using ECB mode (never use block ciphers without a mode)
- Rolling your own crypto (use audited libraries)
- Ignoring authentication (encryption without authentication is insecure)
- Hardcoding keys (use key management systems)
Conclusion
Rust's type system and memory safety make it an excellent choice for implementing cryptographic systems. Combined with well-audited libraries like ring, you can build secure applications with confidence.
In our next post, we'll explore cryptographic protocol implementation and how to avoid common pitfalls when building secure communication systems.
Want to learn more? Check out our GitHub for example projects.