sha2 / bcrypt для Rust: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

sha2 / bcrypt для Rust: полное руководство с примерами

В современной разработке безопасность данных является критическим аспектом любого приложения. Библиотеки sha2 и bcrypt для языка Rust предоставляют мощные и безопасные инструменты для криптографического хеширования. sha2 реализует семейство алгоритмов SHA-2 (Secure Hash Algorithm 2), включая SHA-224, SHA-256, SHA-384, SHA-512, а также их варианты с усечением (SHA-512/224, SHA-512/256). bcrypt — это адаптивная криптографическая хеш-функция, специально разработанная для безопасного хранения паролей. Она использует "соль" (salt) и настраиваемое количество раундов (cost factor), что делает её устойчивой к атакам с использованием радужных таблиц и перебору.

Зачем это нужно? SHA-2 используется для проверки целостности данных, создания цифровых подписей, в системах контроля версий (Git), блокчейне и многих других областях, где требуется быстрый и надёжный хеш. Bcrypt, в свою очередь, является стандартом де-факто для хеширования паролей в веб-приложениях, базах данных и системах аутентификации. Его ключевое преимущество — настраиваемая "медлительность": вы можете увеличить количество раундов, чтобы компенсировать рост вычислительной мощности злоумышленников.

В этой статье мы подробно рассмотрим обе библиотеки: установку, основные методы, реальные примеры использования и типичные ошибки. Вы узнаете, как правильно хешировать данные, проверять пароли и интегрировать эти функции в ваши Rust-проекты.

Установка

Для использования sha2 и bcrypt в Rust необходимо добавить их в файл Cargo.toml вашего проекта. Откройте терминал в корневой директории проекта и выполните:

cargo add sha2 bcrypt

Эта команда добавит последние версии библиотек в раздел [dependencies]. Альтернативно, вы можете вручную отредактировать Cargo.toml:

[dependencies]
sha2 = "0.10"
bcrypt = "0.15"

После этого выполните cargo build, чтобы загрузить зависимости и убедиться, что всё установлено корректно.

Быстрый старт: минимальный рабочий пример

Давайте начнём с простого примера: хеширование строки с помощью SHA-256 и проверка пароля с помощью bcrypt.

use sha2::{Sha256, Digest};
use bcrypt::{hash, verify, DEFAULT_COST};

fn main() {
    // SHA-256 хеширование
    let data = b"Hello, world!";
    let mut hasher = Sha256::new();
    hasher.update(data);
    let result = hasher.finalize();
    println!("SHA-256 хеш: {:x}", result);

    // Bcrypt хеширование пароля
    let password = "my_secure_password";
    let hashed = hash(password, DEFAULT_COST).unwrap();
    println!("Bcrypt хеш: {}", hashed);

    // Проверка пароля
    let is_valid = verify(password, &hashed).unwrap();
    println!("Пароль верен: {}", is_valid);
}

Этот код демонстрирует основные возможности обеих библиотек. Вывод будет содержать 64-символьный hex-хеш SHA-256 и длинную строку bcrypt (начинающуюся с $2b$).

Основные методы и функции

Рассмотрим ключевые функции и методы из sha2 и bcrypt. Для каждой приведём сигнатуру, описание и рабочий пример.

1. sha2::Sha256::new()

Сигнатура: pub fn new() -> Sha256

Описание: Создаёт новый экземпляр хешера SHA-256. Это отправная точка для хеширования данных.

use sha2::{Sha256, Digest};

let mut hasher = Sha256::new();
hasher.update(b"test data");
let hash = hasher.finalize();
println!("{:x}", hash);

2. sha2::Sha256::update()

Сигнатура: pub fn update(&mut self, data: impl AsRef<[u8]>)

Описание: Добавляет данные к текущему состоянию хешера. Можно вызывать несколько раз для потоковой обработки.

use sha2::{Sha256, Digest};

let mut hasher = Sha256::new();
hasher.update(b"part1");
hasher.update(b"part2");
let hash = hasher.finalize();
println!("{:x}", hash);

3. sha2::Sha256::finalize()

Сигнатура: pub fn finalize(self) -> GenericArray<u8, U32>

Описание: Завершает процесс хеширования и возвращает итоговый хеш в виде массива байтов (32 байта для SHA-256).

use sha2::{Sha256, Digest};

let mut hasher = Sha256::new();
hasher.update(b"data");
let hash = hasher.finalize();
// hash - это GenericArray, можно преобразовать в hex
println!("{:x}", hash);

4. sha2::Sha512::new()

Сигнатура: pub fn new() -> Sha512

Описание: Создаёт хешер SHA-512, который возвращает 64-байтовый хеш. Аналогичен Sha256, но с большей длиной.

use sha2::{Sha512, Digest};

let mut hasher = Sha512::new();
hasher.update(b"secure data");
let hash = hasher.finalize();
println!("SHA-512: {:x}", hash);

5. sha2::Sha256::digest() (утилита)

Сигнатура: pub fn digest(data: impl AsRef<[u8]>) -> GenericArray<u8, U32>

Описание: Удобная функция для одноразового хеширования. Создаёт хешер, обновляет его данными и завершает — всё в одном вызове.

use sha2::{Sha256, Digest};

let hash = Sha256::digest(b"quick hash");
println!("{:x}", hash);

6. bcrypt::hash()

Сигнатура: pub fn hash<S: AsRef<[u8]>>(password: S, cost: u32) -> Result<String, BcryptError>

Описание: Хеширует пароль с использованием bcrypt. Параметр cost определяет количество раундов (от 4 до 31). Возвращает строку, содержащую соль и хеш.

use bcrypt::{hash, DEFAULT_COST};

let hashed = hash("my_password", DEFAULT_COST).unwrap();
println!("Хеш: {}", hashed);

7. bcrypt::verify()

Сигнатура: pub fn verify<S: AsRef<[u8]>>(password: S, hashed_password: &str) -> Result<bool, BcryptError>

Описание: Проверяет, соответствует ли пароль указанному хешу bcrypt. Возвращает true или false.

use bcrypt::{hash, verify, DEFAULT_COST};

let password = "secret";
let hashed = hash(password, DEFAULT_COST).unwrap();
let is_valid = verify(password, &hashed).unwrap();
println!("Пароль корректен: {}", is_valid);

8. bcrypt::hash_with_result()

Сигнатура: pub fn hash_with_result<S: AsRef<[u8]>>(password: S, cost: u32) -> Result<BcryptHash, BcryptError>

Описание: Возвращает структуру BcryptHash, содержащую отдельно соль, хеш и параметры. Полезно для кастомной сериализации.

use bcrypt::{hash_with_result, DEFAULT_COST};

let result = hash_with_result("password", DEFAULT_COST).unwrap();
println!("Соль: {:?}", result.salt);
println!("Хеш: {:?}", result.hash);
println!("Параметры: {:?}", result.params);

9. bcrypt::bcrypt() (низкоуровневая)

Сигнатура: pub fn bcrypt(password: &[u8], salt: &[u8], cost: u32) -> Result<Vec<u8>, BcryptError>

Описание: Низкоуровневая функция, выполняющая сам алгоритм bcrypt. Требует явной передачи соли (16 байт). Используется редко, но даёт полный контроль.

use bcrypt::bcrypt;

let password = b"password";
let salt = b"$2b$12$abcdefghijklmnopqrstuv"; // 16 байт после префикса
let cost = 12;
let hash = bcrypt(password, salt, cost).unwrap();
println!("{:?}", hash);

10. bcrypt::format::format_hash()

Сигнатура: pub fn format_hash(salt: &[u8], hash: &[u8], cost: u32) -> String

Описание: Форматирует соль и хеш в стандартную строку bcrypt (например, $2b$12$...). Полезно при ручном формировании хеша.

use bcrypt::format::format_hash;

let salt = b"abcdefghijklmnop"; // 16 байт
let hash = b"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; // 32 байта
let formatted = format_hash(salt, hash, 12);
println!("{}", formatted);

Реальный практический пример

Создадим полноценный пример: простую систему управления пользователями с регистрацией и аутентификацией. Этот код демонстрирует, как использовать sha2 для хеширования email (для быстрого поиска) и bcrypt для безопасного хранения паролей.

use sha2::{Sha256, Digest};
use bcrypt::{hash, verify, DEFAULT_COST};
use std::collections::HashMap;

// Структура пользователя
struct User {
    email_hash: String, // SHA-256 хеш email для быстрого поиска
    password_hash: String, // Bcrypt хеш пароля
}

// Система управления пользователями
struct UserSystem {
    users: HashMap, // ключ - хеш email, значение - пользователь
}

impl UserSystem {
    fn new() -> Self {
        UserSystem {
            users: HashMap::new(),
        }
    }

    // Хеширование email с помощью SHA-256
    fn hash_email(email: &str) -> String {
        let mut hasher = Sha256::new();
        hasher.update(email.as_bytes());
        format!("{:x}", hasher.finalize())
    }

    // Регистрация нового пользователя
    fn register(&mut self, email: &str, password: &str) -> Result<(), String> {
        let email_hash = Self::hash_email(email);
        
        if self.users.contains_key(&email_hash) {
            return Err("Пользователь с таким email уже существует".to_string());
        }

        // Хешируем пароль с bcrypt
        let password_hash = hash(password, DEFAULT_COST)
            .map_err(|e| format!("Ошибка хеширования пароля: {}", e))?;

        let user = User {
            email_hash: email_hash.clone(),
            password_hash,
        };

        self.users.insert(email_hash, user);
        Ok(())
    }

    // Аутентификация пользователя
    fn authenticate(&self, email: &str, password: &str) -> Result {
        let email_hash = Self::hash_email(email);
        
        let user = self.users.get(&email_hash)
            .ok_or_else(|| "Пользователь не найден".to_string())?;

        // Проверяем пароль
        verify(password, &user.password_hash)
            .map_err(|e| format!("Ошибка проверки пароля: {}", e))
    }

    // Изменение пароля
    fn change_password(&mut self, email: &str, old_password: &str, new_password: &str) -> Result<(), String> {
        // Сначала аутентифицируем
        if !self.authenticate(email, old_password)? {
            return Err("Неверный текущий пароль".to_string());
        }

        let email_hash = Self::hash_email(email);
        let user = self.users.get_mut(&email_hash).unwrap();

        // Хешируем новый пароль
        user.password_hash = hash(new_password, DEFAULT_COST)
            .map_err(|e| format!("Ошибка хеширования пароля: {}", e))?;

        Ok(())
    }

    // Удаление пользователя
    fn delete_user(&mut self, email: &str, password: &str) -> Result<(), String> {
        if !self.authenticate(email, password)? {
            return Err("Неверный пароль".to_string());
        }

        let email_hash = Self::hash_email(email);
        self.users.remove(&email_hash);
        Ok(())
    }
}

fn main() {
    let mut system = UserSystem::new();

    // Регистрируем пользователей
    println!("Регистрация пользователей...");
    system.register("alice@example.com", "AlicePass123!").unwrap();
    system.register("bob@example.com", "BobPass456!").unwrap();
    
    // Пытаемся зарегистрировать дубликат
    match system.register("alice@example.com", "AnotherPass") {
        Ok(_) => println!("Успешно (не должно было произойти)"),
        Err(e) => println!("Ошибка (ожидаемо): {}", e),
    }

    // Аутентификация
    println!("\nАутентификация:");
    let auth_result = system.authenticate("alice@example.com", "AlicePass123!");                    

Рекомендаций