Для чего нужна данная статья? :
Реализовать асинхронный TCP-сервер на Tokio, использующий TLS для защиты соединения и HMAC для проверки целостности сообщений.
Зачем Вам это уметь? :
Обучить модель ML для обнаружения аномалий.
1. Использование TLS (Transport Layer Security)
Наиболее распространённый и надёжный способ защиты TCP-соединений — использование TLS. В Rust для этого можно использовать библиотеки:
- rustls
Быстрая и безопасная реализация TLS на Rust.
Пример использования:
use rustls::{ClientConfig, ServerConfig};
// Настройка клиента или сервера с поддержкой TLS
- native-tls
Обёртка для использования системных библиотек TLS (например, OpenSSL, Windows SChannel). - tokio-rustls
Асинхронная интеграция rustls с Tokio для использования в асинхронных приложениях.
2. Шифрование на уровне приложения
Если TLS недоступен или избыточен, можно зашифровать данные вручную перед отправкой.
Библиотеки для шифрования данных:
- AES-GCM
Популярный алгоритм аутентифицированного шифрования.
Пример:
use aes_gcm::{Aes256Gcm, Key, Nonce}; // Or another cipher
use aes_gcm::aead::{Aead, NewAead};
let key = Key::from_slice(b"an example very very secret key."); // 256-битный ключ
let cipher = Aes256Gcm::new(key);
let nonce = Nonce::from_slice(b"unique nonce"); // Уникальный для каждого сообщения
let ciphertext = cipher.encrypt(nonce, b"plaintext message".as_ref())
.expect("encryption failure!");
3. Хеширование и проверка целостности
Для проверки целостности данных можно использовать криптографические хэши (например, HMAC).
- Библиотеки:ring
Быстрая криптографическая библиотека для работы с HMAC, шифрованием и подписью.
sha2
Реализация SHA-256/SHA-512.
Пример HMAC с использованием ring:
use ring::hmac;
let key = hmac::Key::new(hmac::HMAC_SHA256, b"your-secret-key");
let tag = hmac::sign(&key, b"message");
4. Серийные библиотеки для упаковки/шифрования
Некоторые библиотеки обеспечивают безопасность и простоту передачи данных между системами:
- Serde: сериализация данных.
- MessagePack или CBOR: упаковка/сериализация с возможностью шифрования.
5. Использование VPN или SSH-туннелей
Если нет желания или необходимости изменять приложение, можно защитить трафик на уровне сети, используя:
- VPN (например, WireGuard или OpenVPN).
- SSH-туннели для маршрутизации TCP-пакетов через защищённое соединение.
6. Защита от атак (например, DoS, MITM):
- Проверка IP-адресов.
- Защита от повторных подключений через токены или временные метки.
Пример асинхронного TCP-сервера на Tokio, использующего TLS для защиты соединения и HMAC для проверки целостности сообщений.
Для TLS используется библиотека tokio-rustls, а для HMAC — библиотека ring.
use std::sync::Arc;
use tokio::net::TcpListener;
use tokio::prelude::*;
use tokio_rustls::{TlsAcceptor, rustls::{ServerConfig, Certificate, PrivateKey}};
use ring::hmac;
async fn start_tls_server() -> Result<(), Box<dyn std::error::Error>> {
// 1. Настройка TLS
let certs = load_certs("server_cert.pem")?;
let key = load_private_key("server_key.pem")?;
let tls_config = ServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth()
.with_single_cert(certs, key)?;
let acceptor = TlsAcceptor::from(Arc::new(tls_config));
// 2. Настройка TCP-сервера
let listener = TcpListener::bind("127.0.0.1:8080").await?;
println!("Server running on 127.0.0.1:8080");
loop {
let (stream, addr) = listener.accept().await?;
println!("New connection from {}", addr);
// 3. Оборачивание соединения в TLS
let acceptor = acceptor.clone();
tokio::spawn(async move {
match acceptor.accept(stream).await {
Ok(mut tls_stream) => {
println!("TLS handshake successful with {}", addr);
// 4. Чтение данных из клиента
let mut buf = vec![0u8; 1024];
match tls_stream.read(&mut buf).await {
Ok(n) if n > 0 => {
let received_message = &buf[..n];
println!("Received: {:?}", received_message);
// 5. Проверка HMAC
let secret_key = b"supersecretkey";
let key = hmac::Key::new(hmac::HMAC_SHA256, secret_key);
let tag = hmac::sign(&key, received_message);
println!("HMAC tag generated: {:?}", tag.as_ref());
// 6. Отправка данных клиенту
if let Err(e) = tls_stream.write_all(b"Message received").await {
println!("Failed to write: {}", e);
}
}
Ok(_) => println!("Connection closed by client"),
Err(e) => println!("Failed to read: {}", e),
}
}
Err(e) => println!("Failed TLS handshake: {}", e),
}
});
}
}
// Загрузка сертификатов
fn load_certs(filename: &str) -> Result<Vec<Certificate>, Box<dyn std::error::Error>> {
let certfile = std::fs::File::open(filename)?;
let mut reader = std::io::BufReader::new(certfile);
let certs = rustls_pemfile::certs(&mut reader)?
.into_iter()
.map(Certificate)
.collect();
Ok(certs)
}
// Загрузка закрытого ключа
fn load_private_key(filename: &str) -> Result<PrivateKey, Box<dyn std::error::Error>> {
let keyfile = std::fs::File::open(filename)?;
let mut reader = std::io::BufReader::new(keyfile);
let keys: Vec<PrivateKey> = rustls_pemfile::rsa_private_keys(&mut reader)?
.into_iter()
.map(PrivateKey)
.collect();
if keys.is_empty() {
Err("No private keys found".into())
} else {
Ok(keys[0].clone())
}
}
#[tokio::main]
async fn main() {
if let Err(e) = start_tls_server().await {
eprintln!("Error: {}", e);
}
}
Объяснение кода:
- TLS-сертификаты и ключи:Сертификат (server_cert.pem) и закрытый ключ (server_key.pem) необходимы для TLS. Их можно сгенерировать с помощью утилиты openssl.
Пример команды для генерации самоподписанного сертификата:
openssl req -x509 -newkey rsa:2048 -keyout server_key.pem -out server_cert.pem -days 365 -nodes - Асинхронный TCP-сервер с Tokio:Сервер слушает входящие соединения по адресу 127.0.0.1:8080.
Каждое соединение оборачивается в TLS с помощью TlsAcceptor. - HMAC для проверки целостности данных:Используется библиотека ring.
Генерируется HMAC-тег для входящего сообщения с использованием секретного ключа (supersecretkey). - Ответ клиенту:После успешного получения данных сервер отправляет подтверждение обратно клиенту.
Запуск:
Сгенерируйте сертификаты:
openssl req -x509 -newkey rsa:2048 -keyout server_key.pem -out server_cert.pem -days 365 -nodes
Используйте любой TLS-клиент для тестирования соединения (например, openssl s_client):
openssl s_client -connect 127.0.0.1:8080
После подключения отправьте сообщение, и сервер обработает его с HMAC.
Захват TCP-пакетов, извлечение признаков (включая энтропию, размер окна TCP и статистику по портам), использование модели глубокого обучения для обнаружения аномалий, Rayon, блокировка аномальных IP-адресов через iptables и шифрование признаков для защиты системы
- Программа запускает захват пакетов и инициализирует модель машинного обучения.
- Пакеты обрабатываются в реальном времени: извлекаются признаки, шифруются, анализируются моделью.
- Если модель определяет пакет как аномальный (вероятность > 0.5), IP-адрес блокируется.
use pcap::{Capture, Device, Packet};
use tch::{nn, Device as TchDevice, Tensor};
use aes::Aes128;
use aes::cipher::{NewCipher, BlockEncrypt, BlockDecrypt};
use aes::cipher::generic_array::GenericArray;
use rayon::prelude::*;
use std::time::{Instant, Duration};
use log::{info, warn, error};
use env_logger;
use std::collections::HashMap;
use std::process::Command;
// Структура для хранения признаков TCP-пакета
#[derive(Debug, Clone)]
struct TcpFeatures {
length: u32, // Длина пакета
flags: u8, // TCP-флаги (SYN, ACK, FIN и т.д.)
inter_packet_time: f32, // Время между пакетами (в секундах)
packet_rate: f32, // Скорость пакетов в соединении (пакетов/с)
entropy: f32, // Энтропия данных
window_size: u16, // Размер окна TCP
port_count: u32, // Количество пакетов для данного порта
}
// Структура для отслеживания состояния соединения
#[derive(Clone)]
struct ConnectionState {
last_packet_time: Instant,
packet_count: u32,
}
impl ConnectionState {
fn new() -> Self {
ConnectionState {
last_packet_time: Instant::now(),
packet_count: 0,
}
}
// Обновление состояния и вычисление признаков
fn update(&mut self) -> (f32, f32) {
let now = Instant::now();
let elapsed = now.duration_since(self.last_packet_time).as_secs_f32();
self.packet_count += 1;
let packet_rate = if elapsed > 0.0 { self.packet_count as f32 / elapsed } else { 0.0 };
self.last_packet_time = now;
(elapsed, packet_rate)
}
}
// Структура для отслеживания статистики по портам
#[derive(Clone)]
struct PortStats {
port_counts: HashMap<u16, u32>,
}
impl PortStats {
fn new() -> Self {
PortStats { port_counts: HashMap::new() }
}
fn update(&mut self, port: u16) {
*self.port_counts.entry(port).or_insert(0) += 1;
}
fn get_count(&self, port: u16) -> u32 {
*self.port_counts.get(&port).unwrap_or(&0)
}
}
// Захват TCP-пакетов
fn setup_capture() -> Capture<Device> {
let device = Device::lookup().expect("Не удалось найти устройство");
let mut cap = Capture::from_device(device)
.expect("Ошибка инициализации захвата")
.promisc(true) // Включаем promiscuous mode
.timeout(1000) // Тайм-аут 1 секунда
.open()
.expect("Ошибка открытия устройства");
cap.filter("tcp").expect("Ошибка установки фильтра на TCP");
info!("Захват TCP-пакетов настроен на устройстве: {}", device.name);
cap
}
// Вычисление энтропии данных
fn calculate_entropy(data: &[u8]) -> f32 {
let mut frequency = [0u32; 256];
for &byte in data {
frequency[byte as usize] += 1;
}
let total = data.len() as f32;
frequency.iter().filter(|&&f| f > 0).fold(0.0, |acc, &f| {
let p = f as f32 / total;
acc - p * p.log2()
})
}
// Извлечение размера окна TCP
fn extract_window_size(packet: &Packet) -> u16 {
if packet.data.len() >= 14 {
u16::from_be_bytes([packet.data[14], packet.data[15]])
} else {
0
}
}
// Извлечение признаков из TCP-пакета
fn extract_features(packet: &Packet, conn_state: &mut ConnectionState, port_stats: &mut PortStats) -> TcpFeatures {
let length = packet.header.len;
let flags = if packet.data.len() > 13 { packet.data[13] } else { 0 };
let (inter_packet_time, packet_rate) = conn_state.update();
let entropy = calculate_entropy(&packet.data);
let window_size = extract_window_size(packet);
let port = if packet.data.len() >= 2 { u16::from_be_bytes([packet.data[0], packet.data[1]]) } else { 0 };
port_stats.update(port);
let port_count = port_stats.get_count(port);
TcpFeatures {
length,
flags,
inter_packet_time,
packet_rate,
entropy,
window_size,
port_count,
}
}
// Определение модели нейронной сети
struct Net {
fc1: nn::Linear,
fc2: nn::Linear,
fc3: nn::Linear,
}
impl Net {
fn new(vs: &nn::Path) -> Net {
let fc1 = nn::linear(vs / "fc1", 7, 64, Default::default());
let fc2 = nn::linear(vs / "fc2", 64, 32, Default::default());
let fc3 = nn::linear(vs / "fc3", 32, 1, Default::default());
Net { fc1, fc2, fc3 }
}
fn forward(&self, xs: &Tensor) -> Tensor {
xs.apply(&self.fc1).relu()
.apply(&self.fc2).relu()
.apply(&self.fc3).sigmoid()
}
}
// Обучение модели
fn train_model() -> Net {
let vs = nn::VarStore::new(TchDevice::Cpu);
let net = Net::new(&vs.root());
let opt = nn::Adam::default().build(&vs, 1e-3).unwrap();
let features = Tensor::of_slice(&[
100.0, 2.0, 0.1, 10.0, 1.5, 1024.0, 5.0,
150.0, 4.0, 0.05, 20.0, 2.0, 2048.0, 10.0,
500.0, 8.0, 0.01, 100.0, 3.5, 512.0, 1.0,
]).view((3, 7));
let labels = Tensor::of_slice(&[0.0, 0.0, 1.0]).view((-1, 1));
for _ in 0..1000 {
let loss = net.forward(&features).binary_cross_entropy::<Tensor>(&labels, None, tch::Reduction::Mean);
opt.backward_step(&loss);
}
net
}
// Функции шифрования и дешифрования признаков
fn encrypt_features(features: &[f32], key: &GenericArray<u8, <Aes128 as NewCipher>::KeySize>) -> Vec<u8> {
let cipher = Aes128::new(key);
let mut encrypted = Vec::new();
for &feature in features {
let mut block = GenericArray::clone_from_slice(&feature.to_le_bytes());
cipher.encrypt_block(&mut block);
encrypted.extend_from_slice(&block);
}
encrypted
}
fn decrypt_features(encrypted: &[u8], key: &GenericArray<u8, <Aes128 as NewCipher>::KeySize>) -> Vec<f32> {
let cipher = Aes128::new(key);
let mut features = Vec::new();
for chunk in encrypted.chunks(16) {
let mut block = GenericArray::clone_from_slice(chunk);
cipher.decrypt_block(&mut block);
let feature = f32::from_le_bytes([block[0], block[1], block[2], block[3]]);
features.push(feature);
}
features
}
// Обнаружение аномалий
fn detect_anomaly(model: &Net, features: &TcpFeatures, key: &GenericArray<u8, <Aes128 as NewCipher>::KeySize>) -> bool {
let feature_array = [
features.length as f32,
features.flags as f32,
features.inter_packet_time,
features.packet_rate,
features.entropy,
features.window_size as f32,
features.port_count as f32,
];
let encrypted = encrypt_features(&feature_array, key);
let decrypted = decrypt_features(&encrypted, key);
let tensor = Tensor::of_slice(&decrypted).view((1, 7));
let prediction = model.forward(&tensor).double_value(&[0]);
prediction > 0.5
}
// Извлечение IP-адреса источника
fn extract_src_ip(packet: &Packet) -> String {
if packet.data.len() >= 12 {
format!("{}.{}.{}.{}", packet.data[12], packet.data[13], packet.data[14], packet.data[15])
} else {
"0.0.0.0".to_string()
}
}
// Блокировка IP-адреса через iptables
fn block_ip(ip: String) {
let output = Command::new("sudo")
.arg("iptables")
.arg("-A")
.arg("INPUT")
.arg("-s")
.arg(ip)
.arg("-j")
.arg("DROP")
.output();
match output {
Ok(output) if output.status.success() => println!("IP {} заблокирован", ip),
_ => eprintln!("Ошибка блокировки IP {}", ip),
}
}
// Обработка пакетов
async fn process_packets(mut cap: Capture<Device>, model: Net, key: GenericArray<u8, <Aes128 as NewCipher>::KeySize>) {
let mut conn_state = ConnectionState::new();
let mut port_stats = PortStats::new();
loop {
let packets: Vec<Packet> = cap.iter().take(100).collect();
packets.par_iter().for_each(|packet| {
let mut local_conn_state = conn_state.clone();
let mut local_port_stats = port_stats.clone();
let features = extract_features(packet, &mut local_conn_state, &mut local_port_stats);
if detect_anomaly(&model, &features, &key) {
let src_ip = extract_src_ip(packet);
block_ip(src_ip);
}
});
tokio::time::sleep(Duration::from_millis(10)).await;
}
}
// Главная функция
#[tokio::main]
async fn main() {
env_logger::init();
info!("Запуск системы защиты TCP-пакетов");
let cap = setup_capture();
let model = train_model();
let key = GenericArray::from([0u8; 16]); // В реальной системе ключ должен быть секретным
process_packets(cap, model, key).await;
}