Криптография в Rust: шифрование и хеши с библиотеками ring и sha2
Rust — это язык системного программирования, который гарантирует безопасность памяти и высокую производительность. Благодаря этим качествам он идеально подходит для задач криптографии. В этой статье мы рассмотрим две популярные библиотеки: ring (для симметричного шифрования и хеширования) и sha2 (для чистого хеширования). Вы узнаете, как шифровать данные с помощью AES-GCM, вычислять хеши SHA-256 и создавать HMAC-подписи.
Почему Rust для криптографии?
Rust устраняет целые классы уязвимостей (переполнение буфера, гонки данных) на этапе компиляции. Криптографические библиотеки на Rust, такие как ring, написаны на ассемблере и C с безопасными обёртками, что обеспечивает скорость, сравнимую с C, но с надёжностью Rust.
Настройка проекта
Создайте новый проект и добавьте зависимости в Cargo.toml:
[dependencies]
ring = "0.17"
sha2 = "0.10"
hex = "0.4" # для удобного отображения байтов
Симметричное шифрование: AES-GCM с ring
AES-GCM (Galois/Counter Mode) — это современный стандарт симметричного шифрования, который обеспечивает как конфиденциальность, так и аутентификацию (проверку целостности). Библиотека ring предоставляет простой и безопасный API.
Шифрование данных
Для шифрования нужны: ключ (256 бит), nonce (96 бит) и открытый текст. Nonce должен быть уникальным для каждого шифрования с одним ключом.
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);
// Генерация случайного nonce
let mut nonce_bytes = [0u8; 12];
rng.fill(&mut nonce_bytes).unwrap();
let nonce = Nonce::assume_unique_for_key(nonce_bytes);
// Шифрование с пустыми дополнительными данными (AAD)
let mut in_out = plaintext.to_vec();
let tag = key.seal_in_place_separate_tag(nonce, Aad::empty(), &mut in_out).unwrap();
// Возвращаем nonce + зашифрованные данные + тег аутентификации
let mut result = nonce_bytes.to_vec();
result.extend_from_slice(&in_out);
result.extend_from_slice(tag.as_ref());
result
}
Дешифрование
Для дешифрования извлекаем nonce, тег и зашифрованный текст, затем вызываем open_in_place.
fn decrypt_aes_gcm(key: &[u8; 32], ciphertext: &[u8]) -> Option<Vec<u8>> {
if ciphertext.len() < 28 { return None; } // 12 (nonce) + 16 (тег) минимум
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 ожидает тег в конце
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 // неверный ключ или повреждённые данные
}
}
Пример использования
fn main() {
let key = b"0123456789abcdef0123456789abcdef"; // 32 байта
let data = b"Секретное сообщение для Rust!";
let encrypted = encrypt_aes_gcm(key, data);
println!("Зашифровано (hex): {}", hex::encode(&encrypted));
if let Some(decrypted) = decrypt_aes_gcm(key, &encrypted) {
println!("Расшифровано: {}", String::from_utf8(decrypted).unwrap());
} else {
println!("Ошибка дешифрования!");
}
}
Хеширование: SHA-256 с sha2
Хеш-функции преобразуют данные произвольной длины в фиксированный дайджест. SHA-256 — один из самых распространённых алгорит