Курс — Хакинг на Rust. #25 Инструменты хакера на Rust. Криптоанализ и безопасность. Атаки на слабые генераторы случайных чисел
Здравствуйте, дорогие друзья.
Генераторы случайных чисел (ГСЧ) — основа криптографической безопасности. Они используются для создания ключей, солей, токенов и других критических данных. Но если генератор предсказуем, даже самый стойкий алгоритм шифрования становится уязвимым. В этом разделе мы разберём, как обнаруживать и эксплуатировать слабые ГСЧ с помощью Rust.
8.2.1. Почему ГСЧ так важны?
Криптографические ключи должны быть непредсказуемыми. Например, если два пользователя сгенерируют одинаковые ключи из-за слабого ГСЧ, их коммуникация окажется скомпрометированной. В Rust стандартный rand::thread_rng()
считается безопасным, но его можно намеренно ослабить, например, используя фиксированный seed для воспроизводимости.
8.2.2. Типы ГСЧ и их уязвимости
- Детерминированные ГСЧ (PRNG)
Используют seed для генерации последовательности. Если seed угадывается (например, время запуска программы), все «случайные» значения становятся предсказуемыми. - ГСЧ без достаточной энтропии
Если источник энтропии (например, системный/dev/urandom
) исчерпан, генератор выдаёт повторяющиеся или предсказуемые значения. - Некриптографические ГСЧ
Например,rand::rngs::StdRng
с фиксированным seed:
1 2 3 4 5 6 |
use rand::{SeedableRng, Rng}; use rand::rngs::StdRng; let seed = 42; // Фиксированный seed let mut rng = StdRng::seed_from_u64(seed); let weak_key: [u8; 16] = rng.gen(); |
- Зная seed, злоумышленник восстановит ключ.
8.2.3. Атака: Взлом ключа через перебор seed
Сценарий : Программа использует StdRng
с seed, основанным на времени.
Эксплуатация :
- Определить возможный диапазон seed (например, время в миллисекундах).
- Перебрать seed, генерируя ключи и проверяя их на соответствие шифротексту.
Пример кода :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
use rand::{SeedableRng, Rng}; use rand::rngs::StdRng; use aes::Aes128; use block_modes::{BlockMode, Ecb}; use block_modes::block_padding::Pkcs7; use hex_literal::hex; type AesEcb = Ecb<Aes128, Pkcs7>; fn crack_weak_aes(ciphertext: &[u8], known_plaintext: &[u8]) -> Option<[u8; 16]> { let time_range = 1600000000..1700000000; // Диапазон возможных seed for seed in time_range { let mut rng = StdRng::seed_from_u64(seed); let key: [u8; 16] = rng.gen(); let cipher = AesEcb::new_from_slices(&key, Default::default()).unwrap(); if let Ok(decrypted) = cipher.decrypt_vec(ciphertext) { if decrypted.starts_with(known_plaintext) { return Some(key); } } } None } |
Этот код пытается найти ключ, перебирая возможные seed на основе времени.
8.2.4. Атака: Предсказание следующего значения
Если ГСЧ генерирует предсказуемые значения (например, rand::thread_rng()
с известным внутренним состоянием), можно предугадать следующий ключ или токен.
Пример :
1 2 3 4 5 6 7 |
use rand::Rng; fn predict_next_value() { let mut rng = rand::thread_rng(); let values: Vec<u8> = (0..10).map(|_| rng.gen()).collect(); // Злоумышленник анализирует values и восстанавливает состояние RNG } |
В реальных условиях для этого потребуется доступ к нескольким сгенерированным значениям.
8.2.5. Атака: Малая энтропия
Если ключ состоит из малого числа бит (например, 32 бита), его можно перебрать за разумное время.
Пример brute-force на Rust :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
use rayon::prelude::*; use aes::Aes128; use block_modes::{BlockMode, Ecb}; use block_modes::block_padding::Pkcs7; type AesEcb = Ecb<Aes128, Pkcs7>; fn brute_force_aes(ciphertext: &[u8]) -> Option<[u8; 16]> { (0..u32::MAX).into_par_iter() .map(|i| i.to_le_bytes()) .find_map(|key_part| { let mut key = [0u8; 16]; key[..4].copy_from_slice(&key_part); if let Ok(plaintext) = AesEcb::new_from_slices(&key, Default::default()).unwrap().decrypt_vec(ciphertext) { if plaintext.starts_with(b"secret") { Some(key) } else { None } } else { None } }) } |
Здесь ключ ограничен 32 битами, что делает перебор возможным.
8.2.6. Инструменты для анализа ГСЧ
- Тесты NIST
Стандартные тесты для проверки случайности. В Rust можно интегрировать через cratenist-rng
.
1 2 3 4 5 |
use nist_rng::NistStat; let data: Vec<u8> = rand::thread_rng().sample_iter::<u8, _>(rand::distributions::Standard).take(1000).collect(); let stats = NistStat::from_bytes(&data); println!("Entropy: {}", stats.entropy()); |
2. Сниффинг энтропии
Проверка источников энтропии в системе:
1 2 3 4 |
use getrandom::getrandom; let mut buf = [0u8; 16]; getrandom(&mut buf).expect("Ошибка получения энтропии"); |
8.2.7. Защита: Использование криптостойких ГСЧ
- Избегайте фиксированных seed .
- Используйте
rand::thread_rng()
для ключей. - Для особо критичных задач применяйте аппаратные ГСЧ (например,
rdrand
в процессорах Intel).
Пример безопасной генерации ключа :
1 2 3 4 |
use rand::RngCore; let mut key = [0u8; 32]; rand::thread_rng().fill_bytes(&mut key); |
8.2.8. Этический аспект
Атаки на ГСЧ требуют осторожности:
- Используйте знания только для аудита собственных систем.
- Не эксплуатируйте уязвимости без разрешения.
Задачи для самостоятельного решения :
- Реализуйте атаку на ГСЧ, использующий время в секундах как seed.
- Напишите тест для проверки энтропии ключей в вашем проекте.
- Проведите сравнение скорости brute-force для ключей разной длины.
Вывод : Слабые ГСЧ — критическая уязвимость, которую легко игнорировать. Rust предоставляет инструменты для их анализа и защиты, но требует внимательности при реализации.

На этом все. Всем хорошего дня!