Cryptography in Rust: Encryption and Hashing with ring and sha2 Libraries

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Cryptography in Rust: Encryption and Hashing with ring and sha2 Libraries



Rust is a systems programming language that guarantees memory safety and high performance. Thanks to these qualities, it is ideal for cryptography tasks. In this article, we will explore two popular libraries: ring (for symmetric encryption and hashing) and sha2 (for pure hashing). You will learn how to encrypt data using AES-GCM, compute SHA-256 hashes, and create HMAC signatures.



Why Rust for Cryptography?

Rust eliminates entire classes of vulnerabilities (buffer overflow, data races) at compile time. Cryptographic libraries in Rust, such as ring, are written in assembly and C with safe wrappers, providing speed comparable to C but with Rust's reliability.



Project Setup

Create a new project and add dependencies to Cargo.toml:

[dependencies]ring = "0.17"sha2 = "0.10"hex = "0.4"  # for convenient byte display


Symmetric Encryption: AES-GCM with ring

AES-GCM (Galois/Counter Mode) is a modern symmetric encryption standard that provides both confidentiality and authentication (integrity checking). The ring library provides a simple and secure API.



Data Encryption

For encryption you need: a key (256 bits), a nonce (96 bits), and plaintext. The nonce must be unique for each encryption with the same key.

use ring::aead::{Aad, LessSafeKey, Nonce, UnboundKey, AES_256_GCM};use ring::rand::{SecureRandom, SystemRandom};

fn encrypt_aes_gcm(key: &[u8; 32], plaintext: &[u8]) -> Vec<u8> { let rng = SystemRandom::new(); let unbound_key = UnboundKey::new(&AES_256_GCM, key).unwrap(); let key = LessSafeKey::new(unbound_key);

// Generate random nonce let mut nonce_bytes = [0u8; 12]; rng.fill(&mut nonce_bytes).unwrap(); let nonce = Nonce::assume_unique_for_key(nonce_bytes);

// Encryption with empty additional data (AAD) let mut in_out = plaintext.to_vec(); let tag = key.seal_in_place_separate_tag(nonce, Aad::empty(), &mut in_out).unwrap(); // Return nonce + encrypted data + authentication tag let mut result = nonce_bytes.to_vec(); result.extend_from_slice(&in_out); result.extend_from_slice(tag.as_ref()); result}


Decryption

For decryption, extract the nonce, tag, and ciphertext, then call open_in_place.

fn decrypt_aes_gcm(key: &[u8; 32], ciphertext: &[u8]) -> Option<Vec<u8>> {    if ciphertext.len() < 28 { return None; } // 12 (nonce) + 16 (tag) minimum

let (nonce_bytes, rest) = ciphertext.split_at(12); let (encrypted, tag_bytes) = rest.split_at(rest.len() - 16); let nonce = Nonce::assume_unique_for_key(*nonce_bytes); let unbound_key = UnboundKey::new(&AES_256_GCM, key).unwrap(); let key = LessSafeKey::new(unbound_key);

let mut in_out = encrypted.to_vec(); in_out.extend_from_slice(tag_bytes); // ring expects the tag at the end match key.open_in_place(nonce, Aad::empty(), &mut in_out) { Ok(plaintext) => { let plain_len = plaintext.len(); Some(plaintext[..plain_len].to_vec()) }, Err(_) => None // wrong key or corrupted data }}


Usage Example

fn main() {    let key = b"0123456789abcdef0123456789abcdef"; // 32 bytes    let data = b"Secret message for Rust!";        let encrypted = encrypt_aes_gcm(key, data);    println!("Encrypted (hex): {}", hex::encode(&encrypted));        if let Some(decrypted) = decrypt_aes_gcm(key, &encrypted) {        println!("Decrypted: {}", String::from_utf8(decrypted).unwrap())                    

Recommendations