Привет, Хабр!

Сегодня рассмотрим такую библиотеку в Rust, как RustCrypto. RustCrypto — это набор библиотек, реализующих различные криптографические алгоритмы, такие как AES, RSA, SHA, и многие другие.

Симметричное шифрование AES

AES — это стандарт симметричного шифрования, принятый в качестве стандарта федерального правительства США в 2001 году.AES поддерживает различные длины ключей, и на сегодняшний день является одним из самых используемых стандартов.

Чтобы приступить к работе с AES в Rust, необходимо установить соответствующие библиотеки RustCrypto. Для этого открываем файл Cargo.toml и добавляем следующие зависимости:

[dependencies]
aes = "0.7"
aes-gcm = "0.10" # для работы с режимом GCM
rand = "0.8"     # для генерации случайных значений

Начнем с того, как зашифровать текстовые данные с помощью AES-256-GCM:

use aes_gcm::aead::{Aead, KeyInit, OsRng};
use aes_gcm::{Aes256Gcm, Nonce}; // AES-256-GCM
use rand::Rng;

fn encrypt_data(plaintext: &[u8], key: &[u8]) -> Vec<u8> {
    // создаем шифратор с ключом
    let cipher = Aes256Gcm::new(key.into());

    // генерируем случайный nonce
    let nonce = Nonce::from_slice(&rand::thread_rng().gen::<[u8; 12]>()); 

    // шифруем данные
    let ciphertext = cipher.encrypt(nonce, plaintext)
        .expect("Ошибка шифрования!");

    // возвращаем зашифрованные данные и nonce
    [nonce.as_slice(), &ciphertext].concat()
}

fn main() {
    // пример открытого текста
    let plaintext = b"Привет, Хабр! Это секретное сообщение.";

    // генерируем ключ
    let key = rand::thread_rng().gen::<[u8; 32]>(); // 256-битный ключ

    // шифруем данные
    let ciphertext = encrypt_data(plaintext, &key);

    println!("Зашифрованные данные: {:?}", ciphertext);
}

После шифрования данных, следующий шаг – дешифрование:

use aes_gcm::aead::{Aead, KeyInit};
use aes_gcm::{Aes256Gcm, Nonce};

fn decrypt_data(ciphertext: &[u8], key: &[u8]) -> Vec<u8> {
    // создаем дешифратор с ключом
    let cipher = Aes256Gcm::new(key.into());

    // извлекаем nonce (первые 12 байт)
    let nonce = Nonce::from_slice(&ciphertext[..12]);

    // извлекаем зашифрованные данные
    let ciphertext = &ciphertext[12..];

    // дешифруем данные
    let plaintext = cipher.decrypt(nonce, ciphertext)
        .expect("Ошибка дешифрования!");

    plaintext
}

fn main() {
    // пример зашифрованных данных
    let ciphertext = vec![/* ваши зашифрованные данные */];
    
    // используем ранее сгенерированный ключ
    let key = [/* ваш 256-битный ключ */];

    // дешифруем данные
    let plaintext = decrypt_data(&ciphertext, &key);

    println!("Дешифрованные данные: {:?}", String::from_utf8_lossy(&plaintext));
}

Необходимо убедиться, что ключи хранятся в защищенном месте и недоступны для посторонних. В Rust, генерация ключей может быть выполнена с помощью либы rand:

use rand::Rng;

fn generate_key() -> [u8; 32] {
    rand::thread_rng().gen::<[u8; 32]>() // генерируем 256-битный ключ
}

fn main() {
    let key = generate_key();
    println!("Сгенерированный ключ: {:?}", key);
}

Асимметричное шифрование

Асимметричное шифрование использует пару ключей: публичный ключ для шифрования данных и приватный ключ для их расшифровки. Основная фича заключается в том, что данные, зашифрованные публичным ключом, могут быть расшифрованы только соответствующим приватным ключом, и наоборот.

RSA — это один из самых известных и алгоритмов асимметричного шифрования. Рассмотрим его реализую в RustCrypto.

Принципы работы RSA:

  • Генерация ключей: В основе RSA лежит математика с использованием больших простых чисел. Сначала выбираются два случайных простых числа, которые перемножаются для получения модуля n. Публичный ключ состоит из модуля n и экспоненты e, а приватный ключ включает модули n и экспоненту d, получаемую из e и произведения (p-1)*(q-1).

  • Шифрование: Сообщение преобразуется в число, которое возводится в степень e и берётся по модулю n, чтобы получить зашифрованное сообщение.

  • Дешифрование: Зашифрованное сообщение возводится в степень d и берется по модулю n для получения оригинального сообщения.

Для того чтобы начать работу с RSA в Rust, понадобится библиотека rsa, которая входит в состав RustCrypto.

Откроем файл Cargo.toml и добавим:

[dependencies]
rsa = "0.6.0" # версия библиотеки RSA
rand = "0.8"  # для генерации случайных чисел

Начнем с генерации пары ключей:

use rsa::{RsaPrivateKey, RsaPublicKey, PaddingScheme};
use rand::rngs::OsRng; // используем надежный генератор случайных чисел

fn generate_keys() -> (RsaPrivateKey, RsaPublicKey) {
    // генерируем 2048-битный приватный ключ
    let mut rng = OsRng;
    let private_key = RsaPrivateKey::new(&mut rng, 2048)
        .expect("Ошибка генерации ключа");
    
    // получаем публичный ключ из приватного
    let public_key = RsaPublicKey::from(&private_key);

    (private_key, public_key)
}

Теперь зашифруем некоторые данные с использованием публичного ключа:

fn encrypt_message(public_key: &RsaPublicKey, message: &[u8]) -> Vec<u8> {
    let mut rng = OsRng;
    let padding = PaddingScheme::new_pkcs1v15_encrypt(); // используем PKCS1 v1.5 для шифрования

    // шифруем сообщение
    public_key
        .encrypt(&mut rng, padding, message)
        .expect("Ошибка шифрования")
}

fn main() {
    // генерируем ключи
    let (private_key, public_key) = generate_keys();

    // исходное сообщение
    let message = b"Привет, Хабр!";

    // зашифровываем сообщение
    let encrypted_data = encrypt_message(&public_key, message);

    println!("Зашифрованное сообщение: {:?}", encrypted_data);
}

Теперь расшифруем данные с использованием приватного ключа:

fn decrypt_message(private_key: &RsaPrivateKey, encrypted_data: &[u8]) -> Vec<u8> {
    let padding = PaddingScheme::new_pkcs1v15_encrypt();

    // дешифруем сообщение
    private_key
        .decrypt(padding, encrypted_data)
        .expect("Ошибка дешифрования")
}

fn main() {
    // генерируем ключи
    let (private_key, public_key) = generate_keys();

    // исходное сообщение
    let message = b"Привет, Хабр!";

    // зашифровываем сообщение
    let encrypted_data = encrypt_message(&public_key, message);

    // дешифровываем сообщение
    let decrypted_data = decrypt_message(&private_key, &encrypted_data);

    println!("Расшифрованное сообщение: {:?}", String::from_utf8_lossy(&decrypted_data));
}

Симметричное и асимметричное шифрование имеют свои особенности и подходят для разных задач.

Особенность

Симметричное шифрование

Асимметричное шифрование

Ключи

Один ключ для шифрования и дешифрования

Два ключа: публичный для шифрования, приватный для дешифрования

Скорость

Быстрее

Медленнее

Использование

Шифрование данных, где важна скорость

Обмен ключами, цифровые подписи

Управление ключами

Требует безопасного обмена ключами

Не требует обмена приватным ключом

Целостность и аутентификация

Дополнительные механизмы, например, HMAC

Цифровые подписи встроены

HMAC

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

Чтобы начать работу с HMAC, добавляем следующие зависимости в Cargo.toml:

[dependencies]
hmac = "0.12"  # для работы с HMAC
sha2 = "0.10"  # для хэш-функций SHA-2
rand = "0.8"   # для генерации случайных значений

Реализация HMAC с использованием SHA-256:

use hmac::{Hmac, Mac, NewMac};
use sha2::Sha256;

// создаем тип HMAC с SHA-256
type HmacSha256 = Hmac<Sha256>;

fn create_hmac(key: &[u8], data: &[u8]) -> Vec<u8> {
    // создаем новый HMAC
    let mut mac = HmacSha256::new_from_slice(key)
        .expect("Ошибка создания HMAC");

    // пишем данные в HMAC
    mac.update(data);

    // финализируем HMAC и извлекаем хеш-код
    mac.finalize().into_bytes().to_vec()
}

fn main() {
    // пример ключа и данных
    let key = b"секретный_ключ";
    let data = b"Привет, Хабр! Это важное сообщение.";

    // генерируем HMAC
    let hmac_code = create_hmac(key, data);

    println!("HMAC: {:?}", hmac_code);
}

Проверим целостность сообщения с HMAC:

fn verify_hmac(key: &[u8], data: &[u8], expected_hmac: &[u8]) -> bool {
    // создаем новый HMAC
    let mut mac = HmacSha256::new_from_slice(key)
        .expect("Ошибка создания HMAC");

    // пишем данные в HMAC
    mac.update(data);

    // пытаемся проверить HMAC
    mac.verify_slice(expected_hmac).is_ok()
}

fn main() {
    // пример ключа и данных
    let key = b"секретный_ключ";
    let data = b"Привет, Хабр! Это важное сообщение.";

    // генерируем HMAC
    let hmac_code = create_hmac(key, data);

    // проверяем целостность данных
    if verify_hmac(key, data, &hmac_code) {
        println!("Целостность данных подтверждена!");
    } else {
        println!("Целостность данных нарушена!");
    }
}

Подробнее о RustCrypto можно узнать здесь.


Больше про все аспекты программирования эксперты OTUS рассказывают в рамках практических онлайн-курсов. С полным каталогом курсов можно ознакомиться по ссылке.

Комментарии (2)


  1. ALapinskas
    15.08.2024 06:00

    PKCS1 v1.5 для шифрования.

    Вроде как устареший и небезопасный.


    1. domix32
      15.08.2024 06:00

      There are two schemes for encryption and decryption:

      • RSAES-PKCS1-v1_5: older Encryption/decryption Scheme (ES) as first standardized in version 1.5 of PKCS #1. Known-vulnerable.

      • RSAES-OAEP: improved ES; based on the optimal asymmetric encryption padding (OAEP) scheme proposed by Mihir Bellare and Phillip Rogaway. Recommended for new applications.

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