12 Commits

Author SHA1 Message Date
c73f7dc042 Подписи к приложениям 2026-04-04 11:32:33 +03:00
d3a40d4ef7 Переезд на md5 2026-04-04 11:25:35 +03:00
7c83fe5e1a Исправления lab4 2026-04-04 10:21:42 +03:00
642e3cf0c7 lab5 2026-04-02 16:40:25 +03:00
c373e8f5d9 lab4 2026-03-26 09:18:16 +03:00
c62f6284d2 названия файлов в приложениях 2026-03-16 14:46:49 +03:00
8aeb9a4244 lab3 2026-03-16 14:43:38 +03:00
37eff0ed9b lab2 доработки 2026-03-16 12:08:42 +03:00
b6b5e16d63 lab1 -> report 2026-03-03 11:23:13 +03:00
bc1299e966 Отчёт лаб 2 2026-03-03 11:22:28 +03:00
60a4471a8c Код для lab2 2026-03-03 10:21:13 +03:00
07e02401eb Лаба 1 2026-03-03 07:58:06 +03:00
60 changed files with 4852 additions and 345 deletions

View File

@@ -1,345 +0,0 @@
\documentclass[a4paper, final]{article}
%\usepackage{literat} % Нормальные шрифты
\usepackage[14pt]{extsizes} % для того чтобы задать нестандартный 14-ый размер шрифта
\usepackage{tabularx}
\usepackage{booktabs}
\usepackage[T2A]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[russian]{babel}
\usepackage{amsmath}
\usepackage[left=25mm, top=20mm, right=20mm, bottom=20mm, footskip=10mm]{geometry}
\usepackage{ragged2e} %для растягивания по ширине
\usepackage{setspace} %для межстрочного интервала
\usepackage{moreverb} %для работы с листингами
\usepackage{indentfirst} % для абзацного отступа
\usepackage{moreverb} %для печати в листинге исходного кода программ
\usepackage{pdfpages} %для вставки других pdf файлов
\usepackage{tikz}
\usepackage{graphicx}
\usepackage{afterpage}
\usepackage{longtable}
\usepackage{float}
% Рекомендуется для biblatex (кавычки/локализация цитат и т.п.)
\usepackage{csquotes}
% ГОСТ-стили для biblatex
\usepackage[
backend=biber,
bibstyle=gost-numeric, % ссылки вида: [1]
citestyle=gost-numeric,
sorting=none % порядок в списке = по первому цитированию
]{biblatex}
% Все источники хранятся в отдельном файле
\addbibresource{refs.bib}
\renewcommand*{\bibfont}{\small}
% \usepackage[paper=A4,DIV=12]{typearea}
\usepackage{pdflscape}
% \usepackage{lscape}
\usepackage{array}
\usepackage{multirow}
\renewcommand\verbatimtabsize{4\relax}
\renewcommand\listingoffset{0.2em} %отступ от номеров строк в листинге
\renewcommand{\arraystretch}{1.4} % изменяю высоту строки в таблице
\usepackage[font=small, singlelinecheck=false, justification=centering, format=plain, labelsep=period]{caption} %для настройки заголовка таблицы
\usepackage{listings} %листинги
\usepackage{xcolor} % цвета
% \usepackage{hyperref}% для гиперссылок
\usepackage{enumitem} %для перечислений
\newcommand{\specialcell}[2][l]{\begin{tabular}[#1]{@{}l@{}}#2\end{tabular}}
\setlist[enumerate,itemize]{leftmargin=1.2cm} %отступ в перечислениях
% \hypersetup{colorlinks,
% allcolors=[RGB]{010 090 200}} %красивые гиперссылки (не красные)
% подгружаемые языки — подробнее в документации listings (это всё для листингов)
\lstloadlanguages{ SQL}
% включаем кириллицу и добавляем кое−какие опции
\lstset{tabsize=2,
breaklines,
basicstyle=\footnotesize,
columns=fullflexible,
flexiblecolumns,
numbers=left,
numberstyle={\footnotesize},
keywordstyle=\color{blue},
inputencoding=cp1251,
extendedchars=true
}
\lstdefinelanguage{MyC}{
language=SQL,
% ndkeywordstyle=\color{darkgray}\bfseries,
% identifierstyle=\color{black},
% morecomment=[n]{/**}{*/},
% commentstyle=\color{blue}\ttfamily,
% stringstyle=\color{red}\ttfamily,
% morestring=[b]",
% showstringspaces=false,
% morecomment=[l][\color{gray}]{//},
keepspaces=true,
escapechar=\%,
texcl=true
}
\textheight=24cm % высота текста
\textwidth=16cm % ширина текста
\oddsidemargin=0pt % отступ от левого края
\topmargin=-1.5cm % отступ от верхнего края
\parindent=24pt % абзацный отступ
\parskip=5pt % интервал между абзацами
\tolerance=2000 % терпимость к "жидким" строкам
\flushbottom % выравнивание высоты страниц
% Настройка листингов
\lstset{
language=python,
extendedchars=\true,
inputencoding=utf8,
keepspaces=true,
% captionpos=b, % подписи листингов снизу
}
% Настройка содержания
\usepackage{tocloft}
\usepackage[hidelinks]{hyperref}
% section в содержании НЕ жирным
\renewcommand{\cftsecfont}{\normalfont}
\renewcommand{\cftsecpagefont}{\normalfont}
% убрать отступ у subsection
\setlength{\cftsubsecindent}{0pt}
% subsubsection курсивом
\usepackage{titlesec}
\titleformat{\subsubsection}
{\normalfont\large\itshape} % стиль: обычный + курсив
{\thesubsubsection} % номер (убери если не нужен)
{1em}
{}
\begin{document}
% ТИТУЛЬНЫЙ ЛИСТ
\begin{center}
\hfill \break
\hfill \break
\normalsize{МИНИСТЕРСТВО НАУКИ И ВЫСШЕГО ОБРАЗОВАНИЯ РОССИЙСКОЙ ФЕДЕРАЦИИ\\
федеральное государственное автономное образовательное учреждение высшего образования «Санкт-Петербургский политехнический университет Петра Великого»\\[10pt]}
\normalsize{Институт компьютерных наук и кибербезопасности}\\[10pt]
\normalsize{Высшая школа технологий искусственного интеллекта}\\[10pt]
\normalsize{Направление: 02.03.01 <<Математика и компьютерные науки>>}\\
\hfill \break
\hfill \break
\hfill \break
\large{Отчёт по практическим работам по дисциплине}\\
\large{<<Защита информации>>}\\
\hfill \break
\hfill \break
\hfill \break
\end{center}
\small{
\begin{tabular}{lrrl}
\!\!\!Студент, & \hspace{2cm} & & \\
\!\!\!группы 5130201/20101 & \hspace{2cm} & \underline{\hspace{3cm}} & Тищенко А. А. \\\\
\!\!\!Руководитель & \hspace{2cm} & & \\
\!\!\! & \hspace{2cm} & \underline{\hspace{3cm}} & Силиненко А. В. \\\\
&&\hspace{4cm}
\end{tabular}
\begin{flushright}
<<\underline{\hspace{1cm}}>>\underline{\hspace{2.5cm}} 2026г.
\end{flushright}
}
\hfill \break
\hfill \break
\begin{center} \small{Санкт-Петербург, 2026} \end{center}
\thispagestyle{empty} % выключаем отображение номера для этой страницы
\newpage
\tableofcontents
\thispagestyle{empty} % выключаем отображение номера для этой страницы
\newpage
\section*{Введение}
\addcontentsline{toc}{section}{Введение}
В рамках курса <<Защита информации>> было выполнено несколько практических работ, по результатам которых был составлен данный отчёт, содержащий информацию по всем практическим работам. Отчетная информация по каждой работе это отдельный раздел в общем отчете.
В отчёте представлены результаты следующих практических работ:
\begin{enumerate}
\item Практическая работа №1. Анализ уязвимостей программного обеспечения. Данная практическая работа посвящена анализу уязвимостей программного обеспечения с использованием Банка данных угроз безопасности информации ФСТЭК России~\cite{fstec-bdu}. В ходе выполнения работы предусмотрено изучение структуры разделов «Угрозы» и «Уязвимости», а также поиск уязвимостей по заданным критериям. Особое внимание уделяется выявлению уязвимостей, соответствующих используемым версиям операционных систем личных устройств, и рассмотрению возможных мер по их устранению.
\end{enumerate}
\newpage
\section{Практическая работа №1. Анализ уязвимостей программного обеспечения}
\subsection{Знакомство с разделом <<Угрозы>> Банка угроз}
Раздел <<Угрозы>> Банка данных угроз безопасности информации ФСТЭК России предназначен для систематизированного представления сведений об актуальных угрозах безопасности информации. Данный раздел содержит структурированную информацию, позволяющую оценить характер угроз, возможные последствия их реализации и способы противодействия.
Раздел включает в себя следующие основные аспекты:
\begin{enumerate}
\item Классификация угроз: угрозы распределяются по различным категориям в зависимости от источника возникновения, способа реализации и объекта воздействия. Это позволяет упорядочить информацию и упростить её анализ.
\item Описание угроз: для каждой угрозы приводится развернутое описание, включающее возможные сценарии реализации, цели нарушителя и потенциальные последствия для информационной системы.
\item Объекты воздействия: указываются типы информационных систем, ресурсов или процессов, на которые может быть направлена угроза.
\item Уровень опасности: каждой угрозе присваивается определённый уровень опасности, отражающий степень потенциального ущерба при её реализации.
\item Рекомендации по противодействию: приводятся общие меры и подходы, направленные на предупреждение реализации угрозы или снижение возможных негативных последствий.
\end{enumerate}
\subsection{Знакомство с разделом <<Уязвимости>> Банка угроз}
Раздел <<Уязвимости>> Банка данных угроз безопасности информации ФСТЭК России предназначен для получения сведений об актуальных уязвимостях программного обеспечения и их характеристиках. В рамках работы были рассмотрены подразделы: <<Список уязвимостей>>, <<Наиболее опасные уязвимости>> и <<Инфографика>>.
Список уязвимостей представляет собой структурированный перечень выявленных уязвимостей в программном обеспечении. Для каждой уязвимости указывается идентификатор, наименование, описание, затронутое программное обеспечение и его версии, уровень опасности, а также оценка по шкале CVSS. Уязвимости классифицируются по уровню критичности (критический, высокий, средний и др.), что позволяет определить приоритетность их устранения. Также приводятся рекомендации по устранению или минимизации последствий эксплуатации уязвимости.
Подраздел <<Наиболее опасные уязвимости>> содержит перечень уязвимостей с наивысшим уровнем опасности. Как правило, к ним относятся уязвимости, позволяющие выполнить удалённое выполнение кода, повысить привилегии, обойти механизмы аутентификации или получить несанкционированный доступ к информации. Данный раздел позволяет оперативно определить наиболее критичные риски для информационных систем.
\begin{figure}[h!]
\centering
\includegraphics[width=0.7\linewidth]{img/top_vulnerabilities.png}
\caption{Пример отображения наиболее опасных уязвимостей}
\label{fig:top_vuln}
\end{figure}
Подраздел <<Инфографика>> предназначен для наглядного представления статистических данных по уязвимостям. В нём отображается распределение уязвимостей по уровням опасности, типам ошибок, видам программного обеспечения и производителям. Инфографика позволяет визуально оценить текущее состояние защищённости программных продуктов и выявить наиболее проблемные направления.
\begin{figure}[h!]
\centering
\includegraphics[width=0.7\linewidth]{img/vulnerability_stats.png}
\caption{Пример статистического распределения уязвимостей}
\label{fig:stats_vuln}
\end{figure}
\newpage
\subsection{Версии ОС, используемые в личных устройствах}
На личном компьютере используется операционная система Ubuntu 25.10 (см. Рис.~\ref{fig:laptop-os-version}).
\begin{figure}[h!]
\centering
\includegraphics[width=0.5\linewidth]{img/laptop-os-version.png}
\caption{Версия операционной системы личного компьютера}
\label{fig:laptop-os-version}
\end{figure}
На личном мобильном устройстве используется операционная система Android 11 с графической оболочкой MIUI Global 12.5.14, сборка 12.5.14.0 RKURUXM (см. Рис.~\ref{fig:mobile-os-version}).
\begin{figure}[h!]
\centering
\includegraphics[width=0.5\linewidth]{img/mobile-os-version.jpg}
\caption{Версия операционной системы личного мобильного устройства}
\label{fig:mobile-os-version}
\end{figure}
\subsection{Уязвимости ОС личного компьютера}
Для поиска уязвимостей ОС личного компьютера на сайте Банка данных угроз безопасности информации ФСТЭК России были использованы следующие критерии:
\begin{enumerate}
\item Операционная система: Ubuntu 25.10.
\item Уровень опасности: высокий.
\end{enumerate}
Использовался уровень опасности <<высокий>>, так как критических уязвимостей для данной ОС не найдено.
По заданным критериям было найдено 10 уязвимостей (см. Рис.~\ref{fig:laptop-vulnerabilities}).
\begin{figure}[h!]
\centering
\includegraphics[width=0.8\linewidth]{img/laptop-vulnerabilities.png}
\caption{Уязвимости ОС личного компьютера}
\label{fig:laptop-vulnerabilities}
\end{figure}
Наиболее свежей является уязвимость BDU:2025-15300 <<Уязвимость интерфейсов cpu\_latency\_qos\_add, remove, update\_request модуля drivers/ufs/core/ufs-sysfs.c драйвера поддержки устройств SCSI ядра операционной системы Linux связана с ошибками синхронизации при использовании общего ресурса («Ситуация гонки»). Эксплуатация уязвимости может позволить нарушителю, действующему удаленно, вызвать отказ в обслуживании>>. Развёрнутое описание уязвимости представлено на рисунке~\ref{fig:laptop-vulnerabilities-details}.
Базовый вектор уязвимости CVSS 2.0: AV:A/AC:L/Au:S/C:C/I:C/A:C.
AV: A (Access Vector: Adjacent Network) — параметр, указывающий на то, что атакующий должен находиться в той же локальной сети или в непосредственной сетевой близости (например, в одной Wi-Fi сети) для реализации атаки.
AC: L (Access Complexity: Low) — низкая сложность эксплуатации уязвимости. Для успешной атаки не требуется выполнения сложных условий или специальной подготовки среды.
Au: S (Authentication: Single) — для осуществления атаки требуется прохождение аутентификации один раз. Это означает, что атакующий должен обладать учетной записью или иным способом пройти проверку подлинности.
C: C (Confidentiality Impact: Complete) — полное нарушение конфиденциальности. Уязвимость позволяет получить полный доступ к защищаемой информации.
I: C (Integrity Impact: Complete) — полное нарушение целостности. Злоумышленник может изменять или уничтожать данные без ограничений.
A: C (Availability Impact: Complete) — полное нарушение доступности. Эксплуатация уязвимости может привести к отказу в обслуживании или полной недоступности системы.
\begin{figure}[h!]
\centering
\includegraphics[width=0.9\linewidth]{img/laptop-vulnerabilities-details.png}
\caption{Развёрнутое описание уязвимости BDU:2025-15300}
\label{fig:laptop-vulnerabilities-details}
\end{figure}
Уязвимость была устранена в версии 25.10-6.17.0-14.14, поэтому для её устранения достаточно было обновить ОС до этой версии.
\newpage
\subsection{Уязвимости ОС личного мобильного устройства}
Для поиска уязвимостей ОС личного мобильного устройства на сайте Банка данных угроз безопасности информации ФСТЭК России были использованы следующие критерии:
\begin{enumerate}
\item Операционная система: Android 11.
\item Уровень опасности: критический.
\end{enumerate}
По заданным критериям была найдена 1 уязвимость (см. Рис.~\ref{fig:mobile-vulnerabilities}).
\begin{figure}[h!]
\centering
\includegraphics[width=0.8\linewidth]{img/mobile-vulnerabilities.png}
\caption{Уязвимости ОС личного мобильного устройства}
\label{fig:mobile-vulnerabilities}
\end{figure}
Развёрнутое описание уязвимости BDU:2023-08587 <<Уязвимость функции \\ callback\_thread\_event (com\_android\_bluetooth\_btservice\_AdapterService.cpp) операционной системы Android связана с использованием памяти после её освобождения. Эксплуатация уязвимости может позволить нарушителю, действующему удалённо, выполнить произвольный код>>, представлено на рисунке~\ref{fig:mobile-vulnerabilities-details}.
Базовый вектор уязвимости CVSS 2.0: AV:N/AC:L/Au:N/C:C/I:C/A:C.
AV: N (Access Vector: Network) — параметр, указывающий на то, что атакующий может осуществить атаку удалённо через сеть (например, через Интернет), без необходимости физического доступа или нахождения в локальной сети.
AC: L (Access Complexity: Low) — низкая сложность эксплуатации уязвимости. Для успешной атаки не требуется специальных условий или сложной подготовки.
Au: N (Authentication: None) — для эксплуатации уязвимости не требуется аутентификация. Атакующий может выполнить атаку без наличия учетной записи или прохождения процедуры входа в систему.
C: C (Confidentiality Impact: Complete) — полное нарушение конфиденциальности. Уязвимость позволяет злоумышленнику получить полный доступ к конфиденциальной информации.
I: C (Integrity Impact: Complete) — полное нарушение целостности. Атакующий может изменять, подменять или удалять данные без ограничений.
A: C (Availability Impact: Complete) — полное нарушение доступности. Эксплуатация уязвимости может привести к полной недоступности системы или отказу в обслуживании.
\begin{figure}[h!]
\centering
\includegraphics[width=0.9\linewidth]{img/mobile-vulnerabilities-details.png}
\caption{Развёрнутое описание уязвимости BDU:2023-08587}
\label{fig:mobile-vulnerabilities-details}
\end{figure}
\newpage
\section*{Заключение}
\addcontentsline{toc}{section}{Заключение}
В ходе выполнения практической работы №1 был проведён анализ уязвимостей программного обеспечения с использованием Банка данных угроз ФСТЭК России. Были изучены структура разделов <<Угрозы>> и <<Уязвимости>>, определены версии операционных систем личного компьютера и смартфона, а также выполнен поиск уязвимостей с уровнем опасности <<Критический>> и <<Высокий>>. Проведён анализ наиболее актуальных уязвимостей, рассмотрены их характеристики и векторы CVSS, а также рекомендации по устранению. В процессе выполнения работы были получены практические навыки поиска, анализа и оценки уязвимостей информационных систем.
\newpage
\printbibliography[heading=bibintoc]
\end{document}

1
lab2/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.pyc

101
lab2/README.md Normal file
View File

@@ -0,0 +1,101 @@
# Lab 2 — Authentication, Authorization & Brute Force Research
Система доступа к конфиденциальным данным с управлением пользователями и исследованием стойкости паролей.
## Структура каталогов
```
$PRACTICE2_DIR/ # по умолчанию /usr/local/practice2
├── etc/passwd # логин:md5:id:права:ФИО
├── confdata/ # конфиденциальные файлы
├── bin/ # утилиты (usermgr, confaccess, bruteforce)
└── log/ # usermgr.log, access.log
```
Базовый каталог задаётся через переменную окружения `PRACTICE2_DIR`.
Если переменная не задана — используется `/usr/local/practice2`.
## Установка
```bash
chmod +x setup.sh
# для пути по умолчанию (/usr/local/practice2) нужен root:
sudo ./setup.sh
# для тестирования без root:
PRACTICE2_DIR=/tmp/practice2 ./setup.sh
```
Скрипт создаёт структуру каталогов, копирует утилиты в `bin/` и выставляет права доступа.
### Добавить bin во временный PATH
```bash
export PATH="/usr/local/practice2/bin:$PATH"
# или для тестовой директории:
export PATH="/tmp/practice2/bin:$PATH"
```
После этого утилиты доступны без полного пути:
```bash
usermgr add alice
confaccess
bruteforce alice
```
## Использование
### usermgr — управление пользователями
```bash
usermgr add alice # добавить пользователя (интерактивный ввод)
usermgr list # список пользователей
usermgr edit alice --permissions rw
usermgr edit alice --full-name "Иванов Иван"
usermgr passwd alice # сменить пароль
usermgr delete alice # удалить пользователя
```
Права: `r` — чтение, `w` — запись, `d` — удаление.
Требования к паролю: первый символ — буква (AZ, az), далее — буквы, цифры и `!@#$%^&*()`.
### confaccess — доступ к данным
```bash
confaccess
# или с явным указанием базовой директории:
PRACTICE2_DIR=/tmp/practice2 confaccess
```
Режим проверки учётных данных (для bruteforce): `confaccess --check <login>` — читает пароли построчно из stdin, выводит 0 или 1 на каждую строку, exit 0 при первом совпадении.
После аутентификации доступны команды:
```
create <file> создать новый пустой файл в confdata [requires: w]
read <file> вывести содержимое файла [requires: r]
append <file> <text> дописать строку в файл [requires: w]
copy <src> <dst> скопировать файл в confdata [requires: r, w]
remove <file> удалить файл из confdata [requires: d]
help / exit
```
Пути к файлам указываются относительно `confdata/` (или абсолютные).
Для `copy`: src — любой путь, dst — внутри confdata, перезапись запрещена.
Выход — `exit` или Ctrl+C.
### bruteforce — взлом пароля
```bash
bruteforce alice
bruteforce alice --max-length 4
```
Перебор выполняется через утилиту access (confaccess): bruteforce не имеет доступа к passwd-файлу и проверяет пароли только через `confaccess --check`.
Алгоритм хэширования пароля в access: **MD5** (по индивидуальному заданию).
Фиксируется время перебора и количество итераций до нахождения пароля.
Перебор останавливается автоматически при достижении лимита 8 часов.

278
lab2/access.py Normal file
View File

@@ -0,0 +1,278 @@
#!/usr/bin/env python3
import argparse
import getpass
import hashlib
import shutil
import signal
import sys
from datetime import datetime
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
from config import CONFDATA_DIR, LOG_DIR, PASSWD_FILE
HELP_TEXT = """\
Commands:
create <file> create new empty file [requires: w]
read <file> print file contents [requires: r]
append <file> <text> append text line to file [requires: w]
copy <src> <dst> copy file into confdata [requires: r, w]
remove <file> delete file from confdata [requires: d]
help show this help
exit exit"""
def hash_password(password: str) -> str:
return hashlib.md5(password.encode("ascii"), usedforsecurity=False).hexdigest()
def log_action(login: str, action: str) -> None:
LOG_DIR.mkdir(parents=True, exist_ok=True)
timestamp = datetime.now().isoformat(sep=" ", timespec="seconds")
with open(LOG_DIR / "access.log", "a") as f:
f.write(f"{timestamp} [{login}] {action}\n")
def read_users() -> dict[str, dict]:
users: dict[str, dict] = {}
if not PASSWD_FILE.exists():
return users
with open(PASSWD_FILE) as f:
for line in f:
line = line.strip()
if not line:
continue
parts = line.split(":", 4)
if len(parts) != 5:
continue
users[parts[0]] = {
"password_hash": parts[1],
"id": parts[2],
"permissions": parts[3],
"full_name": parts[4],
}
return users
def authenticate() -> tuple[str, dict]:
users = read_users()
while True:
try:
login = input("Login: ").strip()
password = getpass.getpass("Password: ")
except (EOFError, KeyboardInterrupt):
print("\nBye.")
sys.exit(0)
user = users.get(login)
if user and user["password_hash"] == hash_password(password):
return login, user
log_action(login if login else "-", "LOGIN_FAILED")
print("Invalid credentials. Try again.")
def confdata_path(arg: str) -> Path:
p = Path(arg)
if not p.is_absolute():
p = CONFDATA_DIR / p
return p.resolve()
def is_in_confdata(path: Path) -> bool:
try:
path.relative_to(CONFDATA_DIR.resolve())
return True
except ValueError:
return False
def cmd_read(args: list[str], login: str, perms: str) -> None:
if "r" not in perms:
print("Permission denied (requires: r)")
return
if len(args) != 1:
print("Usage: read <file>")
return
path = confdata_path(args[0])
if not is_in_confdata(path):
print("Access denied: file must be inside confdata")
return
if not path.exists():
print(f"File not found: {path.name}")
return
print(path.read_text(), end="")
log_action(login, f"READ {path}")
def cmd_create(args: list[str], login: str, perms: str) -> None:
if "w" not in perms:
print("Permission denied (requires: w)")
return
if len(args) != 1:
print("Usage: create <file>")
return
path = confdata_path(args[0])
if not is_in_confdata(path):
print("Access denied: file must be inside confdata")
return
if path.exists():
print(f"File already exists: {path.name}")
return
path.parent.mkdir(parents=True, exist_ok=True)
path.touch()
log_action(login, f"CREATE {path}")
print("Done.")
def cmd_append(args: list[str], login: str, perms: str) -> None:
if "w" not in perms:
print("Permission denied (requires: w)")
return
if len(args) < 2:
print("Usage: append <file> <text>")
return
path = confdata_path(args[0])
if not is_in_confdata(path):
print("Access denied: file must be inside confdata")
return
if not path.exists():
print(f"File not found: {path.name}. Use 'create' first.")
return
text = " ".join(args[1:])
with open(path, "a") as f:
f.write(text + "\n")
log_action(login, f"APPEND {path}")
print("Done.")
def cmd_copy(args: list[str], login: str, perms: str) -> None:
if "r" not in perms or "w" not in perms:
print("Permission denied (requires: r, w)")
return
if len(args) != 2:
print("Usage: copy <src> <dst>")
return
src = Path(args[0]).resolve()
dst = confdata_path(args[1])
if not is_in_confdata(dst):
print("Access denied: destination must be inside confdata")
return
if dst.is_dir():
dst = dst / src.name
if dst.exists():
print(f"Destination already exists: {dst.name}")
return
if not src.exists():
print(f"Source not found: {args[0]}")
return
if src.is_dir():
print("Copying directories is not supported")
return
shutil.copy2(src, dst)
log_action(login, f"COPY {src} -> {dst}")
print("Done.")
def cmd_remove(args: list[str], login: str, perms: str) -> None:
if "d" not in perms:
print("Permission denied (requires: d)")
return
if len(args) != 1:
print("Usage: remove <file>")
return
path = confdata_path(args[0])
if not is_in_confdata(path):
print("Access denied: file must be inside confdata")
return
if not path.exists():
print(f"File not found: {path.name}")
return
path.unlink()
log_action(login, f"REMOVE {path}")
print("Done.")
def check_credentials(login: str, password: str) -> bool:
"""Check login+password against passwd. Used by --check mode."""
users = read_users()
user = users.get(login)
if user and user["password_hash"] == hash_password(password):
return True
return False
def main() -> None:
parser = argparse.ArgumentParser(description="Access confidential data")
parser.add_argument(
"--check",
metavar="LOGIN",
help="Batch mode: read passwords line-by-line from stdin, output 0 or 1 per line; exit 0 on first match",
)
args, _ = parser.parse_known_args()
if args.check is not None:
# --check: one process, many checks; read_users once
users = read_users()
user = users.get(args.check)
target_hash = user["password_hash"] if user else None
for line in sys.stdin:
password = line.rstrip("\n")
if target_hash and hash_password(password) == target_hash:
sys.stdout.write("1\n")
sys.stdout.flush()
sys.exit(0)
sys.stdout.write("0\n")
sys.stdout.flush()
sys.exit(1)
signal.signal(signal.SIGINT, lambda _s, _f: (print("\nBye."), sys.exit(0)))
login, user = authenticate()
perms = user["permissions"]
full_name = user["full_name"]
log_action(login, "LOGIN")
print(f"\nПривет, {full_name}")
print(HELP_TEXT)
while True:
try:
line = input(f"\n{login}> ").strip()
except (EOFError, KeyboardInterrupt):
log_action(login, "EXIT")
print("\nBye.")
break
if not line:
continue
parts = line.split()
command, args = parts[0], parts[1:]
if command == "exit":
log_action(login, "EXIT")
print("Bye.")
break
elif command == "help":
print(HELP_TEXT)
elif command == "create":
cmd_create(args, login, perms)
elif command == "read":
cmd_read(args, login, perms)
elif command == "append":
cmd_append(args, login, perms)
elif command == "copy":
cmd_copy(args, login, perms)
elif command == "remove":
cmd_remove(args, login, perms)
else:
print(f"Unknown command: {command!r}. Type 'help' for available commands.")
if __name__ == "__main__":
main()

141
lab2/bruteforce.py Normal file
View File

@@ -0,0 +1,141 @@
#!/usr/bin/env python3
import argparse
import itertools
import shutil
import string
import subprocess
import sys
import time
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
from config import BIN_DIR
CHARSET = string.ascii_letters + string.digits + "!@#$%^&*()"
FIRST_CHARS = string.ascii_letters
MAX_HOURS = 8
def get_access_cmd() -> list[str]:
"""Resolve path to access utility (confaccess)."""
# setup.sh installs as confaccess; pyproject exposes as access
for name in ("confaccess", "access"):
path = shutil.which(name)
if path:
return [path]
# Fallback: same directory as bruteforce (development)
script_dir = Path(__file__).resolve().parent
access_script = script_dir / "access.py"
if access_script.exists():
return [sys.executable, str(access_script)]
# Try bin dir from config (e.g. /usr/local/practice2/bin/confaccess)
for name in ("confaccess", "access"):
bin_cmd = BIN_DIR / name
if bin_cmd.exists():
return [str(bin_cmd)]
return ["confaccess"] # hope it's in PATH
def max_combinations(length: int) -> int:
if length == 1:
return len(FIRST_CHARS)
return len(FIRST_CHARS) * (len(CHARSET) ** (length - 1))
def brute_force_length(
login: str, length: int, proc: subprocess.Popen[str]
) -> tuple[str, int, float] | None:
"""Try all passwords of given length via batch process. proc = access --check."""
count = 0
start = time.perf_counter()
stdin = proc.stdin
stdout = proc.stdout
assert stdin is not None and stdout is not None
def check(password: str) -> bool:
nonlocal count
count += 1
stdin.write(password + "\n")
stdin.flush()
line = stdout.readline()
if not line:
return False
return line.strip() == "1"
if length == 1:
for first in FIRST_CHARS:
if time.perf_counter() - start > MAX_HOURS * 3600:
return None
if check(first):
return first, count, time.perf_counter() - start
return None
for first in FIRST_CHARS:
for rest in itertools.product(CHARSET, repeat=length - 1):
if time.perf_counter() - start > MAX_HOURS * 3600:
return None
password = first + "".join(rest)
if check(password):
return password, count, time.perf_counter() - start
return None
def main() -> None:
parser = argparse.ArgumentParser(
description="Brute force password cracker (via access utility)"
)
parser.add_argument("login", help="Target username")
parser.add_argument(
"--max-length",
type=int,
default=6,
help="Maximum password length to try (default: 6)",
)
args = parser.parse_args()
cmd = get_access_cmd() + ["--check", args.login]
proc = subprocess.Popen(
cmd,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
text=True,
bufsize=1,
)
try:
print(f"Target: {args.login}")
print(f"Charset size: {len(CHARSET)} ({len(FIRST_CHARS)} valid for first char)")
print()
total_start = time.perf_counter()
for length in range(1, args.max_length + 1):
total = max_combinations(length)
print(f"Length {length}: max {total:>15,} combinations")
result = brute_force_length(args.login, length, proc)
if result is not None:
password, count, elapsed = result
total_elapsed = time.perf_counter() - total_start
print(f" >>> FOUND: '{password}'")
print(f" Iterations: {count:,}")
print(f" Time (len): {elapsed:.4f}s")
print(f" Time (total): {total_elapsed:.4f}s")
print(f" Speed: {count / elapsed:,.0f} attempts/s")
return
else:
print(f" Not found at length {length} (timeout or exhausted)")
print(f"\nPassword not found within length {args.max_length}.")
finally:
if proc.stdin:
proc.stdin.close()
if proc.poll() is None:
proc.terminate()
proc.wait()
if __name__ == "__main__":
main()

11
lab2/config.py Normal file
View File

@@ -0,0 +1,11 @@
import os
from pathlib import Path
BASE_DIR = Path(os.environ.get("PRACTICE2_DIR", "/usr/local/practice2"))
ETC_DIR = BASE_DIR / "etc"
CONFDATA_DIR = BASE_DIR / "confdata"
BIN_DIR = BASE_DIR / "bin"
LOG_DIR = BASE_DIR / "log"
PASSWD_FILE = ETC_DIR / "passwd"

284
lab2/lab2.md Normal file
View File

@@ -0,0 +1,284 @@
# Практическая работа №2
по дисциплине «Защита информации»
**Тема работы:** «Разработка и исследование системы аутентификации и авторизации»
**Преподаватель:** Силиненко А.В.
**Email:** [a_silinenko@mail.ru](mailto:a_silinenko@mail.ru)
---
## 1. Цели работы
* Разработать систему доступа пользователей к конфиденциальным данным;
* Исследовать стойкость паролей к атаке методом грубой силы.
---
## 2. Задачи работы
### 2.1.
При необходимости установить на компьютер целевую ОС (Linux или MacOS), в которой производится разработка и использование системы.
### 2.2.
Разработать систему доступа пользователей к конфиденциальным данным, включающую:
* Выделенный каталог для хранения всех файлов системы;
* Утилиту для работы с данными аутентификации и авторизации (паролями и правами доступа);
* Утилиту доступа к конфиденциальным данным, обеспечивающую аутентификацию и авторизацию пользователя.
### 2.3.
Разработать программу взлома паролей методом грубой силы и исследовать стойкость паролей в зависимости от длины пароля и алгоритма хэширования.
---
## 3. Требования к работе
### 3.1. ОС
Работа выполняется в ОС **Linux** или **MacOS**.
Необходим доступ с правами суперпользователя.
Если доступа нет — установить гипервизор **VirtualBox** ([https://www.virtualbox.org/](https://www.virtualbox.org/)) и развернуть гостевую ОС.
Допускается установка ОС как второй системы без VirtualBox.
---
### 3.2. Структура каталогов
Необходимо создать дерево каталогов:
```
/usr/local/practice2/
├── etc # хранение данных аутентификации и авторизации
├── confdata # хранение конфиденциальных файлов
├── bin # разработанные утилиты
└── log # файлы регистрации
```
**Права доступа:** чтение, запись и выполнение только для пользователя `root`.
---
### 3.3. Требования к утилите управления пользователями
#### Файл хранения данных
Файл:
```
/usr/local/practice2/etc/passwd
```
Структура записи:
```
<логин>:<хэш_пароля>:<идентификатор>:<права>:<ФИО>
```
Одна строка — один пользователь.
**Права на файл:** чтение и запись только для `root`.
#### Права доступа
* `r` — чтение
* `w` — запись
* `d` — удаление
#### Требования к функционалу
Утилита должна обеспечивать:
* Добавление нового пользователя:
* логин
* ФИО
* права доступа
* пароль + подтверждение
* Проверку корректности данных
* Хранение пароля в виде **хэш-значения**
* Использование алгоритма хэширования согласно индивидуальному заданию
* Редактирование существующего пользователя
* Изменение пароля
* Удаление пользователя
* Регистрацию всех действий с файлом `passwd`
#### Требования к паролю
* Кодировка: ASCII
* Разрешены:
* AZ
* az
* 09
* `!@#$%^&*()`
* Первый символ не может быть цифрой или спецсимволом
---
### 3.4. Требования к утилите доступа к конфиденциальным данным
#### Авторизация
При запуске:
1. Запрос логина
2. Запрос пароля
3. Проверка корректности
Если данные корректны:
* Вывод: `Привет, <ФИО>`
* Краткая справка
* Приглашение к вводу команд
Если данные некорректны:
* Повторный запрос
Остановка — по сигналу **SIGINT (Ctrl+C)**.
---
### Поддерживаемые команды
| Команда | Описание | Требуемые права |
| -------- | ------------------------ | --------------- |
| `read` | Вывод содержимого файла | r |
| `append` | Добавление данных в файл | w |
| `copy` | Копирование файла | r + w |
| `remove` | Удаление файла | d |
| `exit` | Выход | — |
| `help` | Справка | — |
#### Ограничения copy
* Разрешено копирование:
* в каталог `confdata`
* внутри `confdata`
* Запрещено:
* из `confdata` в другие каталоги
* перезапись существующих файлов внутри `confdata`
#### Дополнительные требования
* Утилита доступна для запуска всем пользователям ОС
* Все действия с конфиденциальными данными логируются
---
### 3.5. Программа взлома паролей
Должна:
* Запускать утилиту доступа
* Перебирать пароли методом brute force
* Учитывать используемый алгоритм хэширования
* Фиксировать:
* время перебора
* количество итераций до взлома
---
### 3.6. Общие требования
* Язык программирования — любой
* Код должен быть снабжен комментариями
---
### 3.7. Требования к исследованию
Провести исследование для длин паролей:
```
3, 4, 5, 6, 7, 8 символов
```
Необходимо:
* Рассчитать максимальное количество итераций
* По экспериментам (34 символа) оценить время для 58
* Проверить теорию экспериментально
* Прервать эксперимент, если длительность > 8 часов
* Проводить тестирование без активных задач на ПК
---
## 4. Требования к отчету
В отчете необходимо указать:
* Актуальность темы
* Цель и задачи
* Требования к системе
* Характеристики ПК (процессор, память)
* ОС и среду разработки
* Используемый язык
* Алгоритм хэширования
* Примеры сборки (если применимо)
* Примеры работы утилит
* Пример расчета количества итераций и времени взлома
* Таблицу или график с результатами:
* рассчитанное количество итераций и время
* полученные экспериментальные данные
* Выводы
---
## 5. Справочная информация
### Поддержка алгоритмов хэширования
**Linux:**
* `md5sum`
* `sha1sum`
* `sha256sum`
* `sha512sum`
* `b2sum`
* библиотека `openssl`
**C++:**
* `std::hash`
**Python:**
* модуль `hashlib`
**Java:**
* `java.security.MessageDigest`
* Spring Security
**Go:**
* пакет `hash`
---
Примечание:
Brute Force должен запускать утилиту из-под себя и через неё пытаться подобрать пароль, он не должен лезть в файл напрямую.

38
lab2/setup.sh Executable file
View File

@@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BASE_DIR="${PRACTICE2_DIR:-/usr/local/practice2}"
echo "Setting up directory structure at: $BASE_DIR"
mkdir -p "$BASE_DIR/etc" "$BASE_DIR/confdata" "$BASE_DIR/bin" "$BASE_DIR/log"
touch "$BASE_DIR/etc/passwd"
cp "$SCRIPT_DIR/config.py" "$BASE_DIR/bin/config.py"
cp "$SCRIPT_DIR/usermgr.py" "$BASE_DIR/bin/usermgr"
cp "$SCRIPT_DIR/access.py" "$BASE_DIR/bin/confaccess"
cp "$SCRIPT_DIR/bruteforce.py" "$BASE_DIR/bin/bruteforce"
chmod +x "$BASE_DIR/bin/usermgr" "$BASE_DIR/bin/confaccess" "$BASE_DIR/bin/bruteforce"
if [ "$(id -u)" -eq 0 ]; then
chmod 700 "$BASE_DIR" "$BASE_DIR/etc" "$BASE_DIR/confdata" "$BASE_DIR/bin" "$BASE_DIR/log"
chmod 600 "$BASE_DIR/etc/passwd"
echo "Permissions set (root-only)."
else
echo "Warning: not running as root; skipping permission hardening."
fi
echo ""
echo "Done. Directory layout:"
ls -la "$BASE_DIR"
echo ""
echo "Next steps:"
echo " $BASE_DIR/bin/usermgr add <login>"
echo " $BASE_DIR/bin/confaccess"
echo " $BASE_DIR/bin/bruteforce <login>"
echo ""
echo "To add bin to PATH temporarily:"
echo " export PATH=\"$BASE_DIR/bin:\$PATH\""

246
lab2/usermgr.py Normal file
View File

@@ -0,0 +1,246 @@
#!/usr/bin/env python3
import argparse
import getpass
import hashlib
import sys
from datetime import datetime
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
from config import LOG_DIR, PASSWD_FILE
ALLOWED_CHARS = set(
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()"
)
FIRST_CHAR_ALLOWED = set("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")
VALID_PERM_CHARS = set("rwd")
def hash_password(password: str) -> str:
return hashlib.md5(password.encode("ascii"), usedforsecurity=False).hexdigest()
def validate_password(password: str) -> str | None:
if not password:
return "password cannot be empty"
if password[0] not in FIRST_CHAR_ALLOWED:
return "first character must be a letter (A-Z, a-z)"
invalid = [ch for ch in password if ch not in ALLOWED_CHARS]
if invalid:
return f"invalid characters: {''.join(set(invalid))!r}"
return None
def validate_permissions(perms: str) -> str | None:
if not perms:
return "permissions cannot be empty"
for ch in perms:
if ch not in VALID_PERM_CHARS:
return f"invalid permission {ch!r}; allowed: r, w, d"
if len(set(perms)) != len(perms):
return "duplicate permissions"
return None
def log_action(action: str) -> None:
LOG_DIR.mkdir(parents=True, exist_ok=True)
timestamp = datetime.now().isoformat(sep=" ", timespec="seconds")
with open(LOG_DIR / "usermgr.log", "a") as f:
f.write(f"{timestamp} {action}\n")
def read_users() -> list[dict]:
if not PASSWD_FILE.exists():
return []
users = []
with open(PASSWD_FILE) as f:
for line in f:
line = line.strip()
if not line:
continue
parts = line.split(":", 4)
if len(parts) != 5:
continue
users.append(
{
"login": parts[0],
"password_hash": parts[1],
"id": parts[2],
"permissions": parts[3],
"full_name": parts[4],
}
)
return users
def write_users(users: list[dict]) -> None:
PASSWD_FILE.parent.mkdir(parents=True, exist_ok=True)
with open(PASSWD_FILE, "w") as f:
for u in users:
f.write(
f"{u['login']}:{u['password_hash']}:{u['id']}:{u['permissions']}:{u['full_name']}\n"
)
def find_user(users: list[dict], login: str) -> dict | None:
return next((u for u in users if u["login"] == login), None)
def next_uid(users: list[dict]) -> str:
if not users:
return "1"
return str(max(int(u["id"]) for u in users) + 1)
def prompt_password() -> str:
while True:
password = getpass.getpass("Password: ")
err = validate_password(password)
if err:
print(f"Invalid password: {err}")
continue
confirm = getpass.getpass("Confirm password: ")
if password != confirm:
print("Passwords do not match.")
continue
return password
def cmd_add(args: argparse.Namespace) -> None:
users = read_users()
login = args.login
if find_user(users, login):
print(f"User '{login}' already exists.")
sys.exit(1)
full_name = input("Full name: ").strip()
if not full_name:
print("Full name cannot be empty.")
sys.exit(1)
perms = input("Permissions (any combination of r, w, d): ").strip()
err = validate_permissions(perms)
if err:
print(f"Invalid permissions: {err}")
sys.exit(1)
password = prompt_password()
uid = next_uid(users)
users.append(
{
"login": login,
"password_hash": hash_password(password),
"id": uid,
"permissions": perms,
"full_name": full_name,
}
)
write_users(users)
log_action(f"ADD login={login} id={uid} permissions={perms} full_name='{full_name}'")
print(f"User '{login}' added (id={uid}).")
def cmd_edit(args: argparse.Namespace) -> None:
users = read_users()
user = find_user(users, args.login)
if not user:
print(f"User '{args.login}' not found.")
sys.exit(1)
changed = []
if args.full_name is not None:
if not args.full_name:
print("Full name cannot be empty.")
sys.exit(1)
user["full_name"] = args.full_name
changed.append("full_name")
if args.permissions is not None:
err = validate_permissions(args.permissions)
if err:
print(f"Invalid permissions: {err}")
sys.exit(1)
user["permissions"] = args.permissions
changed.append("permissions")
if not changed:
print("Nothing to change. Use --full-name and/or --permissions.")
sys.exit(0)
write_users(users)
log_action(f"EDIT login={args.login} changed={','.join(changed)}")
print(f"User '{args.login}' updated: {', '.join(changed)}.")
def cmd_passwd(args: argparse.Namespace) -> None:
users = read_users()
user = find_user(users, args.login)
if not user:
print(f"User '{args.login}' not found.")
sys.exit(1)
password = prompt_password()
user["password_hash"] = hash_password(password)
write_users(users)
log_action(f"PASSWD login={args.login}")
print(f"Password for '{args.login}' updated.")
def cmd_delete(args: argparse.Namespace) -> None:
users = read_users()
if not find_user(users, args.login):
print(f"User '{args.login}' not found.")
sys.exit(1)
users = [u for u in users if u["login"] != args.login]
write_users(users)
log_action(f"DELETE login={args.login}")
print(f"User '{args.login}' deleted.")
def cmd_list(_args: argparse.Namespace) -> None:
users = read_users()
if not users:
print("No users.")
return
print(f"{'Login':<16} {'ID':<4} {'Perms':<6} Full Name")
print("-" * 52)
for u in users:
print(f"{u['login']:<16} {u['id']:<4} {u['permissions']:<6} {u['full_name']}")
def main() -> None:
parser = argparse.ArgumentParser(description="User management utility for practice2")
sub = parser.add_subparsers(dest="command", required=True)
p_add = sub.add_parser("add", help="Add a new user")
p_add.add_argument("login", help="Username (login)")
p_add.set_defaults(func=cmd_add)
p_edit = sub.add_parser("edit", help="Edit an existing user")
p_edit.add_argument("login", help="Username to edit")
p_edit.add_argument("--full-name", dest="full_name", help="New full name")
p_edit.add_argument("--permissions", help="New permissions (e.g. rwd)")
p_edit.set_defaults(func=cmd_edit)
p_passwd = sub.add_parser("passwd", help="Change user password")
p_passwd.add_argument("login", help="Username")
p_passwd.set_defaults(func=cmd_passwd)
p_del = sub.add_parser("delete", help="Delete a user")
p_del.add_argument("login", help="Username to delete")
p_del.set_defaults(func=cmd_delete)
p_list = sub.add_parser("list", help="List all users")
p_list.set_defaults(func=cmd_list)
args = parser.parse_args()
args.func(args)
if __name__ == "__main__":
main()

2
lab3/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
*.pyc
*.pdf

94
lab3/README.md Normal file
View File

@@ -0,0 +1,94 @@
# Lab 3 — DAC + MAC
Система доступа к конфиденциальным данным с дискреционным (DAC) и мандатным (MAC) управлением доступом. Основана на Lab 2.
## Структура каталогов
```
$PRACTICE3_DIR/ # по умолчанию /usr/local/practice3
├── etc/
│ ├── passwd # логин:sha256:id:права:ФИО
│ ├── access_mode # BOTH | DAC_ONLY | MAC_ONLY
│ ├── acl # матрица доступа (DAC)
│ ├── subject_labels # метки субъектов (MAC)
│ └── object_labels # метки объектов (MAC)
├── confdata/ # конфиденциальные файлы
├── bin/ # usermgr, confaccess, bruteforce
└── log/ # usermgr.log, access.log
```
## Режимы проверки доступа
| Режим | DAC | MAC |
|-------|-----|-----|
| BOTH (по умолчанию) | да | да |
| DAC_ONLY | да | нет |
| MAC_ONLY | нет | да |
Режим задаётся через `usermgr set-mode` (только root).
## Установка
```bash
chmod +x setup.sh
# для пути по умолчанию нужен root:
sudo ./setup.sh
# для тестирования без root:
PRACTICE3_DIR=/tmp/practice3 ./setup.sh
```
## Использование
### usermgr — управление пользователями
```bash
usermgr add alice
usermgr list
usermgr edit alice --full-name "Иванов Иван" --permissions rw
usermgr edit alice --label 1 # метка субъекта (root only)
usermgr set-label report.txt 2 # метка объекта (root only)
usermgr set-mode DAC_ONLY # режим проверки (root only)
usermgr show-mode # текущий режим (root only)
usermgr passwd alice
usermgr delete alice
```
### confaccess — доступ к данным
```bash
confaccess
PRACTICE3_DIR=/tmp/practice3 confaccess
```
Команды после аутентификации:
```
create <file> создать файл (владелец = текущий пользователь)
read <file> прочитать файл
append <file> <text> дописать в файл
copy <src> <dst> скопировать в confdata
remove <file> удалить файл
grant <user> <path> <perms> выдать права (только владелец)
help / exit
```
### DAC (дискреционный доступ)
- Каждый объект имеет владельца (создатель)
- Владелец может выдавать права через `grant <user> <path> <perms>`
- Права: r (чтение), w (запись), d (удаление)
### MAC (мандатный доступ, Белл–Лападула)
- Метки: 0 — несекретно, 1 — ДСП, 2 — секретно
- Нет чтения сверху: субъект читает только объекты с уровнем ≤ своего
- Нет записи вниз: субъект пишет только в объекты с уровнем ≥ своего
### bruteforce
```bash
bruteforce alice
bruteforce alice --max-length 4
```

138
lab3/bruteforce.py Normal file
View File

@@ -0,0 +1,138 @@
#!/usr/bin/env python3
import argparse
import itertools
import shutil
import string
import subprocess
import sys
import time
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
from config import BIN_DIR
CHARSET = string.ascii_letters + string.digits + "!@#$%^&*()"
FIRST_CHARS = string.ascii_letters
MAX_HOURS = 8
def get_access_cmd() -> list[str]:
"""Resolve path to confaccess utility."""
for name in ("confaccess", "access"):
path = shutil.which(name)
if path:
return [path]
script_dir = Path(__file__).resolve().parent
confaccess_script = script_dir / "confaccess.py"
if confaccess_script.exists():
return [sys.executable, str(confaccess_script)]
for name in ("confaccess", "access"):
bin_cmd = BIN_DIR / name
if bin_cmd.exists():
return [str(bin_cmd)]
return ["confaccess"]
def max_combinations(length: int) -> int:
if length == 1:
return len(FIRST_CHARS)
return len(FIRST_CHARS) * (len(CHARSET) ** (length - 1))
def brute_force_length(
login: str, length: int, proc: subprocess.Popen[str]
) -> tuple[str, int, float] | None:
"""Try all passwords of given length via batch process. proc = confaccess --check."""
count = 0
start = time.perf_counter()
stdin = proc.stdin
stdout = proc.stdout
assert stdin is not None and stdout is not None
def check(password: str) -> bool:
nonlocal count
count += 1
stdin.write(password + "\n")
stdin.flush()
line = stdout.readline()
if not line:
return False
return line.strip() == "1"
if length == 1:
for first in FIRST_CHARS:
if time.perf_counter() - start > MAX_HOURS * 3600:
return None
if check(first):
return first, count, time.perf_counter() - start
return None
for first in FIRST_CHARS:
for rest in itertools.product(CHARSET, repeat=length - 1):
if time.perf_counter() - start > MAX_HOURS * 3600:
return None
password = first + "".join(rest)
if check(password):
return password, count, time.perf_counter() - start
return None
def main() -> None:
parser = argparse.ArgumentParser(
description="Brute force password cracker (via confaccess utility)"
)
parser.add_argument("login", help="Target username")
parser.add_argument(
"--max-length",
type=int,
default=6,
help="Maximum password length to try (default: 6)",
)
args = parser.parse_args()
cmd = get_access_cmd() + ["--check", args.login]
proc = subprocess.Popen(
cmd,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
text=True,
bufsize=1,
)
try:
print(f"Target: {args.login}")
print(f"Charset size: {len(CHARSET)} ({len(FIRST_CHARS)} valid for first char)")
print()
total_start = time.perf_counter()
for length in range(1, args.max_length + 1):
total = max_combinations(length)
print(f"Length {length}: max {total:>15,} combinations")
result = brute_force_length(args.login, length, proc)
if result is not None:
password, count, elapsed = result
total_elapsed = time.perf_counter() - total_start
print(f" >>> FOUND: '{password}'")
print(f" Iterations: {count:,}")
print(f" Time (len): {elapsed:.4f}s")
print(f" Time (total): {total_elapsed:.4f}s")
print(f" Speed: {count / elapsed:,.0f} attempts/s")
return
else:
print(f" Not found at length {length} (timeout or exhausted)")
print(f"\nPassword not found within length {args.max_length}.")
finally:
if proc.stdin:
proc.stdin.close()
if proc.poll() is None:
proc.terminate()
proc.wait()
if __name__ == "__main__":
main()

518
lab3/confaccess.py Normal file
View File

@@ -0,0 +1,518 @@
#!/usr/bin/env python3
"""Access to confidential data with DAC and MAC."""
import argparse
import getpass
import hashlib
import shutil
import signal
import sys
from datetime import datetime
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
from config import (
ACL_FILE,
ACCESS_MODE_FILE,
CONFDATA_DIR,
LOG_DIR,
OBJECT_LABELS_FILE,
PASSWD_FILE,
SUBJECT_LABELS_FILE,
)
HELP_TEXT = """\
Commands:
create <file> create new empty file
read <file> print file contents
append <file> <text> append text line to file
copy <src> <dst> copy file into confdata
remove <file> delete file from confdata
grant <user> <path> <perms> grant access (owner only)
help show this help
exit exit"""
def hash_password(password: str) -> str:
return hashlib.sha256(password.encode("ascii")).hexdigest()
def log_action(login: str, action: str) -> None:
LOG_DIR.mkdir(parents=True, exist_ok=True)
timestamp = datetime.now().isoformat(sep=" ", timespec="seconds")
with open(LOG_DIR / "access.log", "a") as f:
f.write(f"{timestamp} [{login}] {action}\n")
def read_users() -> dict[str, dict]:
users: dict[str, dict] = {}
if not PASSWD_FILE.exists():
return users
with open(PASSWD_FILE) as f:
for line in f:
line = line.strip()
if not line:
continue
parts = line.split(":", 4)
if len(parts) != 5:
continue
users[parts[0]] = {
"password_hash": parts[1],
"id": parts[2],
"permissions": parts[3],
"full_name": parts[4],
}
return users
def read_access_mode() -> str:
if not ACCESS_MODE_FILE.exists():
return "BOTH"
raw = ACCESS_MODE_FILE.read_text().strip().upper()
if raw in ("BOTH", "DAC_ONLY", "MAC_ONLY"):
return raw
return "BOTH"
def normalize_path(path: str) -> str:
"""Normalize path relative to confdata (no ./, no trailing /)."""
p = path.strip().lstrip("./")
parts = [x for x in p.split("/") if x]
return "/".join(parts) if parts else ""
def read_acl() -> dict[str, dict]:
"""Read ACL: path -> {owner, perms: {user: perms_str}}."""
acl: dict[str, dict] = {}
if not ACL_FILE.exists():
return acl
for line in ACL_FILE.read_text().splitlines():
line = line.strip()
if not line:
continue
parts = line.split(":", 2)
if len(parts) < 3:
continue
path, owner, assignments = parts[0], parts[1], parts[2]
path = normalize_path(path)
perms: dict[str, str] = {}
for ass in assignments.split(","):
if ":" in ass:
u, p = ass.split(":", 1)
perms[u.strip()] = p.strip()
acl[path] = {"owner": owner, "perms": perms}
return acl
def write_acl(acl: dict[str, dict]) -> None:
ACL_FILE.parent.mkdir(parents=True, exist_ok=True)
lines = []
for path in sorted(acl.keys()):
entry = acl[path]
owner = entry["owner"]
perms = entry["perms"]
ass = ",".join(f"{u}:{p}" for u, p in sorted(perms.items()))
lines.append(f"{path}:{owner}:{ass}")
ACL_FILE.write_text("\n".join(lines) + "\n")
def read_subject_labels() -> dict[str, int]:
labels: dict[str, int] = {}
if not SUBJECT_LABELS_FILE.exists():
return labels
for line in SUBJECT_LABELS_FILE.read_text().splitlines():
line = line.strip()
if not line:
continue
parts = line.split(":", 1)
if len(parts) == 2 and parts[1] in ("0", "1", "2"):
labels[parts[0]] = int(parts[1])
return labels
def read_object_labels() -> dict[str, int]:
labels: dict[str, int] = {}
if not OBJECT_LABELS_FILE.exists():
return labels
for line in OBJECT_LABELS_FILE.read_text().splitlines():
line = line.strip()
if not line:
continue
parts = line.split(":", 1)
if len(parts) == 2 and parts[1] in ("0", "1", "2"):
labels[parts[0]] = int(parts[1])
return labels
def write_object_labels(labels: dict[str, int]) -> None:
OBJECT_LABELS_FILE.parent.mkdir(parents=True, exist_ok=True)
lines = [f"{path}:{level}" for path, level in sorted(labels.items())]
OBJECT_LABELS_FILE.write_text("\n".join(lines) + "\n")
def dac_allows(login: str, action: str, rel_path: str) -> bool:
"""Check DAC: does login have required permission on object?"""
rel_path = normalize_path(rel_path)
acl = read_acl()
if action in ("create", "create_dst"):
# New object: not in ACL yet, any authenticated user can create
if rel_path not in acl:
return True
entry = acl.get(rel_path)
if not entry:
return False
perms = entry["perms"].get(login, "")
if action == "read":
return "r" in perms
if action in ("write", "append", "create_dst"):
return "w" in perms
if action == "remove":
return "d" in perms
if action == "grant":
return entry["owner"] == login
return False
def dac_owner(rel_path: str) -> str | None:
"""Return owner of path or None."""
rel_path = normalize_path(rel_path)
acl = read_acl()
entry = acl.get(rel_path)
return entry["owner"] if entry else None
def mac_allows(login: str, action: str, rel_path: str, obj_level: int | None = None) -> bool:
"""Check MAC (Bell-LaPadula)."""
labels = read_subject_labels()
subj_level = labels.get(login, 0)
obj_labels = read_object_labels()
norm_path = normalize_path(rel_path)
if obj_level is None:
obj_level = obj_labels.get(norm_path)
if action == "read":
# No read up: subject_level >= object_level
obj_lev = obj_level if obj_level is not None else 0
return subj_level >= obj_lev
if action in ("write", "append", "remove"):
obj_lev = obj_level if obj_level is not None else 0
return subj_level <= obj_lev
if action == "create_dst":
# New object gets subject's label; subject can write to same level
return True
return True
def check_access(login: str, action: str, rel_path: str, mode: str) -> bool:
if mode in ("BOTH", "DAC_ONLY"):
if not dac_allows(login, action, rel_path):
return False
if mode in ("BOTH", "MAC_ONLY"):
if not mac_allows(login, action, rel_path):
return False
return True
def authenticate() -> tuple[str, dict]:
users = read_users()
while True:
try:
login = input("Login: ").strip()
password = getpass.getpass("Password: ")
except (EOFError, KeyboardInterrupt):
print("\nBye.")
sys.exit(0)
user = users.get(login)
if user and user["password_hash"] == hash_password(password):
return login, user
log_action(login if login else "-", "LOGIN_FAILED")
print("Invalid credentials. Try again.")
def confdata_path(arg: str) -> Path:
p = Path(arg)
if not p.is_absolute():
p = CONFDATA_DIR / p
return p.resolve()
def rel_path_from_confdata(path: Path) -> str:
try:
rel = path.relative_to(CONFDATA_DIR.resolve())
return "/".join(rel.parts)
except ValueError:
return ""
def is_in_confdata(path: Path) -> bool:
try:
path.relative_to(CONFDATA_DIR.resolve())
return True
except ValueError:
return False
def cmd_read(args: list[str], login: str, mode: str) -> None:
if len(args) != 1:
print("Usage: read <file>")
return
path = confdata_path(args[0])
if not is_in_confdata(path):
print("Access denied: file must be inside confdata")
return
if not path.exists():
print(f"File not found: {path.name}")
return
rel = rel_path_from_confdata(path)
if not check_access(login, "read", rel, mode):
print("Permission denied")
return
print(path.read_text(), end="")
log_action(login, f"READ {path}")
def cmd_create(args: list[str], login: str, mode: str) -> None:
if len(args) != 1:
print("Usage: create <file>")
return
path = confdata_path(args[0])
if not is_in_confdata(path):
print("Access denied: file must be inside confdata")
return
if path.exists():
print(f"File already exists: {path.name}")
return
rel = rel_path_from_confdata(path)
# DAC: new object, add to ACL with owner=login
# MAC: new object gets subject's label
if mode in ("BOTH", "DAC_ONLY"):
acl = read_acl()
acl[rel] = {"owner": login, "perms": {login: "rwd"}}
write_acl(acl)
if mode in ("BOTH", "MAC_ONLY"):
labels = read_subject_labels()
subj_level = labels.get(login, 0)
obj_labels = read_object_labels()
obj_labels[rel] = subj_level
write_object_labels(obj_labels)
path.parent.mkdir(parents=True, exist_ok=True)
path.touch()
log_action(login, f"CREATE {path}")
print("Done.")
def cmd_append(args: list[str], login: str, mode: str) -> None:
if len(args) < 2:
print("Usage: append <file> <text>")
return
path = confdata_path(args[0])
if not is_in_confdata(path):
print("Access denied: file must be inside confdata")
return
if not path.exists():
print(f"File not found: {path.name}. Use 'create' first.")
return
rel = rel_path_from_confdata(path)
if not check_access(login, "append", rel, mode):
print("Permission denied")
return
text = " ".join(args[1:])
with open(path, "a") as f:
f.write(text + "\n")
log_action(login, f"APPEND {path}")
print("Done.")
def cmd_copy(args: list[str], login: str, mode: str) -> None:
if len(args) != 2:
print("Usage: copy <src> <dst>")
return
src = Path(args[0]).resolve()
dst = confdata_path(args[1])
if not is_in_confdata(dst):
print("Access denied: destination must be inside confdata")
return
if dst.is_dir():
dst = dst / src.name
if dst.exists():
print(f"Destination already exists: {dst.name}")
return
if not src.exists():
print(f"Source not found: {args[0]}")
return
if src.is_dir():
print("Copying directories is not supported")
return
dst_rel = rel_path_from_confdata(dst)
src_rel = rel_path_from_confdata(src) if is_in_confdata(src) else None
if src_rel is not None:
if not check_access(login, "read", src_rel, mode):
print("Permission denied (read source)")
return
if not check_access(login, "create_dst", dst_rel, mode):
print("Permission denied (create destination)")
return
shutil.copy2(src, dst)
if mode in ("BOTH", "DAC_ONLY"):
acl = read_acl()
acl[dst_rel] = {"owner": login, "perms": {login: "rwd"}}
write_acl(acl)
if mode in ("BOTH", "MAC_ONLY"):
labels = read_subject_labels()
subj_level = labels.get(login, 0)
obj_labels = read_object_labels()
obj_labels[dst_rel] = subj_level
write_object_labels(obj_labels)
log_action(login, f"COPY {src} -> {dst}")
print("Done.")
def cmd_remove(args: list[str], login: str, mode: str) -> None:
if len(args) != 1:
print("Usage: remove <file>")
return
path = confdata_path(args[0])
if not is_in_confdata(path):
print("Access denied: file must be inside confdata")
return
if not path.exists():
print(f"File not found: {path.name}")
return
rel = rel_path_from_confdata(path)
if not check_access(login, "remove", rel, mode):
print("Permission denied")
return
path.unlink()
if mode in ("BOTH", "DAC_ONLY"):
acl = read_acl()
acl.pop(rel, None)
write_acl(acl)
if mode in ("BOTH", "MAC_ONLY"):
obj_labels = read_object_labels()
obj_labels.pop(rel, None)
write_object_labels(obj_labels)
log_action(login, f"REMOVE {path}")
print("Done.")
def cmd_grant(args: list[str], login: str, mode: str) -> None:
if mode not in ("BOTH", "DAC_ONLY"):
print("grant is only available in DAC or BOTH mode")
return
if len(args) != 3:
print("Usage: grant <user> <path> <perms>")
return
target_user, path_arg, perms = args[0], args[1], args[2]
for ch in perms:
if ch not in "rwd":
print(f"Invalid permission {ch!r}; allowed: r, w, d")
return
path = confdata_path(path_arg)
if not is_in_confdata(path):
print("Access denied: path must be inside confdata")
return
if not path.exists():
print(f"File not found: {path_arg}")
return
rel = rel_path_from_confdata(path)
if not dac_allows(login, "grant", rel):
print("Permission denied (only owner can grant)")
return
acl = read_acl()
entry = acl.get(rel)
if not entry:
print("ACL entry not found")
return
entry["perms"][target_user] = perms
write_acl(acl)
log_action(login, f"GRANT {target_user} {rel} {perms}")
print("Done.")
def check_credentials(login: str, password: str) -> bool:
"""Check login+password against passwd. Used by --check mode."""
users = read_users()
user = users.get(login)
if user and user["password_hash"] == hash_password(password):
return True
return False
def main() -> None:
parser = argparse.ArgumentParser(description="Access confidential data")
parser.add_argument(
"--check",
metavar="LOGIN",
help="Batch mode: read passwords line-by-line from stdin, output 0 or 1 per line; exit 0 on first match",
)
args, _ = parser.parse_known_args()
if args.check is not None:
users = read_users()
user = users.get(args.check)
target_hash = user["password_hash"] if user else None
for line in sys.stdin:
password = line.rstrip("\n")
if target_hash and hash_password(password) == target_hash:
sys.stdout.write("1\n")
sys.stdout.flush()
sys.exit(0)
sys.stdout.write("0\n")
sys.stdout.flush()
sys.exit(1)
signal.signal(signal.SIGINT, lambda _s, _f: (print("\nBye."), sys.exit(0)))
login, user = authenticate()
full_name = user["full_name"]
mode = read_access_mode()
log_action(login, "LOGIN")
print(f"\nПривет, {full_name}")
print(HELP_TEXT)
while True:
try:
line = input(f"\n{login}> ").strip()
except (EOFError, KeyboardInterrupt):
log_action(login, "EXIT")
print("\nBye.")
break
if not line:
continue
parts = line.split()
command, cmd_args = parts[0], parts[1:]
if command == "exit":
log_action(login, "EXIT")
print("Bye.")
break
elif command == "help":
print(HELP_TEXT)
elif command == "create":
cmd_create(cmd_args, login, mode)
elif command == "read":
cmd_read(cmd_args, login, mode)
elif command == "append":
cmd_append(cmd_args, login, mode)
elif command == "copy":
cmd_copy(cmd_args, login, mode)
elif command == "remove":
cmd_remove(cmd_args, login, mode)
elif command == "grant":
cmd_grant(cmd_args, login, mode)
else:
print(f"Unknown command: {command!r}. Type 'help' for available commands.")
if __name__ == "__main__":
main()

15
lab3/config.py Normal file
View File

@@ -0,0 +1,15 @@
import os
from pathlib import Path
BASE_DIR = Path(os.environ.get("PRACTICE3_DIR", "/usr/local/practice3"))
ETC_DIR = BASE_DIR / "etc"
CONFDATA_DIR = BASE_DIR / "confdata"
BIN_DIR = BASE_DIR / "bin"
LOG_DIR = BASE_DIR / "log"
PASSWD_FILE = ETC_DIR / "passwd"
ACCESS_MODE_FILE = ETC_DIR / "access_mode"
ACL_FILE = ETC_DIR / "acl"
SUBJECT_LABELS_FILE = ETC_DIR / "subject_labels"
OBJECT_LABELS_FILE = ETC_DIR / "object_labels"

108
lab3/lab3.md Normal file
View File

@@ -0,0 +1,108 @@
# Практическая работа №3
**Дисциплина:** Защита информации
**Тема:** Реализация моделей дискреционного и мандатного управления доступом
**Преподаватель:** Силиненко А.В.
**Email:** a_silinenko@mail.ru
---
## 1. Цели работы
- Изучить особенности моделей дискреционного (DAC) и мандатного (MAC) управления доступом.
- На базе разработанной в практической работе 2 системы аутентификации и авторизации реализовать DAC и MAC.
---
## 2. Задачи работы
### 2.1
Изучить особенности дискреционного (произвольного) управления доступом, а также модель Харрисона–Руззо–Ульмана, основанную на DAC.
### 2.2
На базе разработанной в практической работе 2 системы аутентификации и авторизации разработать систему, реализующую DAC, включая:
- владение объектами;
- произвольное назначение прав доступа к объектам;
- контроль на основе матрицы или списка доступа.
### 2.3
Изучить особенности мандатного (принудительного) управления доступом, а также модель Белла–Лападулы, основанную на MAC.
### 2.4
На базе разработанной в практической работе 2 системы аутентификации и авторизации разработать систему, реализующую MAC, включая:
- метки безопасности для субъектов и объектов;
- свойства безопасности модели Белла–Лападулы.
---
## 3. Требования к работе
### 3.1
Работа выполняется в ОС **Linux** или **MacOS**.
### 3.2
Для выполнения работы необходимо создать отдельное дерево каталогов, например:
```
/usr/local/practice3/etc файлы настроек
/usr/local/practice3/confdata файлы с конфиденциальной информацией
/usr/local/practice3/bin исполняемые файлы
/usr/local/practice3/log файлы регистрации
```
Права доступа к созданным каталогам: **чтение, запись и выполнение только для пользователя root**.
При необходимости возможно создание дополнительных каталогов.
---
### 3.3 Требования к системе DAC
- у каждого объекта должен быть владелец, который может произвольно назначать и передавать права доступа к этому объекту;
- контроль доступа к объектам должен осуществляться на основе **матрицы или списка доступа**, где хранится информация о правах доступа субъектов к объектам.
---
### 3.4 Требования к системе MAC
- каждому субъекту и объекту должна присваиваться **метка безопасности суперпользователем**;
- предусмотреть **не менее трех уровней меток безопасности**, например:
- несекретно
- для служебного пользования (ДСП)
- секретно
- субъекты не могут менять метки безопасности;
- контроль доступа должен осуществляться на основе свойств безопасности **модели Белла–Лападулы**:
- «нет чтения сверху»
- «нет записи вниз»
- дискреционное свойство
---
## 4. Требования к отчету
В разделе отчета должны быть приведены:
- актуальность темы работы в контексте дисциплины;
- цель и задачи работы;
- требования к разрабатываемым системам;
- краткие сведения о DAC и MAC;
- примеры компиляции и сборки приложений (если используется компилируемый язык);
- примеры запуска и работы утилит доступа к конфиденциальной информации;
- примеры содержимого служебных файлов:
- матрицы (или списка) доступа для DAC
- меток конфиденциальности для MAC;
- выводы по проделанной работе.
---
Примечание
Та система котрая нами уже разработана в lab2
1. Необходимо выпустить ее реинкарнацию с учетом требований к тому что это должен быть дискреционный подход к управлению доступа
Свойства системы которые необходимо будет реализовать будет в задании
2. Необходимо реализовать мандатный подход к управлению доступом
Структура остается прежней нам просто нужно добавить компоненты для первого и второго подходов

43
lab3/setup.sh Executable file
View File

@@ -0,0 +1,43 @@
#!/usr/bin/env bash
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BASE_DIR="${PRACTICE3_DIR:-/usr/local/practice3}"
echo "Setting up directory structure at: $BASE_DIR"
mkdir -p "$BASE_DIR/etc" "$BASE_DIR/confdata" "$BASE_DIR/bin" "$BASE_DIR/log"
touch "$BASE_DIR/etc/passwd"
echo "BOTH" > "$BASE_DIR/etc/access_mode"
touch "$BASE_DIR/etc/acl"
touch "$BASE_DIR/etc/subject_labels"
touch "$BASE_DIR/etc/object_labels"
cp "$SCRIPT_DIR/config.py" "$BASE_DIR/bin/config.py"
cp "$SCRIPT_DIR/usermgr.py" "$BASE_DIR/bin/usermgr"
cp "$SCRIPT_DIR/confaccess.py" "$BASE_DIR/bin/confaccess"
cp "$SCRIPT_DIR/bruteforce.py" "$BASE_DIR/bin/bruteforce"
chmod +x "$BASE_DIR/bin/usermgr" "$BASE_DIR/bin/confaccess" "$BASE_DIR/bin/bruteforce"
if [ "$(id -u)" -eq 0 ]; then
chmod 700 "$BASE_DIR" "$BASE_DIR/etc" "$BASE_DIR/confdata" "$BASE_DIR/bin" "$BASE_DIR/log"
chmod 600 "$BASE_DIR/etc/passwd" "$BASE_DIR/etc/access_mode" \
"$BASE_DIR/etc/acl" "$BASE_DIR/etc/subject_labels" "$BASE_DIR/etc/object_labels"
echo "Permissions set (root-only)."
else
echo "Warning: not running as root; skipping permission hardening."
fi
echo ""
echo "Done. Directory layout:"
ls -la "$BASE_DIR"
echo ""
echo "Next steps:"
echo " $BASE_DIR/bin/usermgr add <login>"
echo " $BASE_DIR/bin/confaccess"
echo " $BASE_DIR/bin/bruteforce <login>"
echo ""
echo "To add bin to PATH temporarily:"
echo " export PATH=\"$BASE_DIR/bin:\$PATH\""

378
lab3/usermgr.py Normal file
View File

@@ -0,0 +1,378 @@
#!/usr/bin/env python3
import argparse
import getpass
import hashlib
import os
import sys
from datetime import datetime
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
from config import (
ACCESS_MODE_FILE,
ETC_DIR,
LOG_DIR,
OBJECT_LABELS_FILE,
PASSWD_FILE,
SUBJECT_LABELS_FILE,
)
ALLOWED_CHARS = set(
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()"
)
FIRST_CHAR_ALLOWED = set("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")
VALID_PERM_CHARS = set("rwd")
VALID_LABELS = frozenset({"0", "1", "2"})
def require_root() -> None:
"""Exit if not running as root."""
if os.geteuid() != 0:
print("Must run as root", file=sys.stderr)
sys.exit(1)
def hash_password(password: str) -> str:
return hashlib.sha256(password.encode("ascii")).hexdigest()
def validate_password(password: str) -> str | None:
if not password:
return "password cannot be empty"
if password[0] not in FIRST_CHAR_ALLOWED:
return "first character must be a letter (A-Z, a-z)"
invalid = [ch for ch in password if ch not in ALLOWED_CHARS]
if invalid:
return f"invalid characters: {''.join(set(invalid))!r}"
return None
def validate_permissions(perms: str) -> str | None:
if not perms:
return "permissions cannot be empty"
for ch in perms:
if ch not in VALID_PERM_CHARS:
return f"invalid permission {ch!r}; allowed: r, w, d"
if len(set(perms)) != len(perms):
return "duplicate permissions"
return None
def log_action(action: str) -> None:
LOG_DIR.mkdir(parents=True, exist_ok=True)
timestamp = datetime.now().isoformat(sep=" ", timespec="seconds")
with open(LOG_DIR / "usermgr.log", "a") as f:
f.write(f"{timestamp} {action}\n")
def read_users() -> list[dict]:
if not PASSWD_FILE.exists():
return []
users = []
with open(PASSWD_FILE) as f:
for line in f:
line = line.strip()
if not line:
continue
parts = line.split(":", 4)
if len(parts) != 5:
continue
users.append(
{
"login": parts[0],
"password_hash": parts[1],
"id": parts[2],
"permissions": parts[3],
"full_name": parts[4],
}
)
return users
def write_users(users: list[dict]) -> None:
PASSWD_FILE.parent.mkdir(parents=True, exist_ok=True)
with open(PASSWD_FILE, "w") as f:
for u in users:
f.write(
f"{u['login']}:{u['password_hash']}:{u['id']}:{u['permissions']}:{u['full_name']}\n"
)
def find_user(users: list[dict], login: str) -> dict | None:
return next((u for u in users if u["login"] == login), None)
def next_uid(users: list[dict]) -> str:
if not users:
return "1"
return str(max(int(u["id"]) for u in users) + 1)
def prompt_password() -> str:
while True:
password = getpass.getpass("Password: ")
err = validate_password(password)
if err:
print(f"Invalid password: {err}")
continue
confirm = getpass.getpass("Confirm password: ")
if password != confirm:
print("Passwords do not match.")
continue
return password
def read_access_mode() -> str:
"""Read access mode; default BOTH if file missing."""
if not ACCESS_MODE_FILE.exists():
return "BOTH"
raw = ACCESS_MODE_FILE.read_text().strip().upper()
if raw in ("BOTH", "DAC_ONLY", "MAC_ONLY"):
return raw
return "BOTH"
def write_access_mode(mode: str) -> None:
ETC_DIR.mkdir(parents=True, exist_ok=True)
ACCESS_MODE_FILE.write_text(mode + "\n")
def read_subject_labels() -> dict[str, int]:
"""Read subject labels; login -> level (0, 1, 2)."""
labels: dict[str, int] = {}
if not SUBJECT_LABELS_FILE.exists():
return labels
for line in SUBJECT_LABELS_FILE.read_text().splitlines():
line = line.strip()
if not line:
continue
parts = line.split(":", 1)
if len(parts) == 2 and parts[1] in VALID_LABELS:
labels[parts[0]] = int(parts[1])
return labels
def write_subject_labels(labels: dict[str, int]) -> None:
ETC_DIR.mkdir(parents=True, exist_ok=True)
lines = [f"{login}:{level}" for login, level in sorted(labels.items())]
SUBJECT_LABELS_FILE.write_text("\n".join(lines) + "\n")
def read_object_labels() -> dict[str, int]:
"""Read object labels; path (relative to confdata) -> level."""
labels: dict[str, int] = {}
if not OBJECT_LABELS_FILE.exists():
return labels
for line in OBJECT_LABELS_FILE.read_text().splitlines():
line = line.strip()
if not line:
continue
parts = line.split(":", 1)
if len(parts) == 2 and parts[1] in VALID_LABELS:
labels[parts[0]] = int(parts[1])
return labels
def write_object_labels(labels: dict[str, int]) -> None:
ETC_DIR.mkdir(parents=True, exist_ok=True)
lines = [f"{path}:{level}" for path, level in sorted(labels.items())]
OBJECT_LABELS_FILE.write_text("\n".join(lines) + "\n")
def cmd_add(args: argparse.Namespace) -> None:
users = read_users()
login = args.login
if find_user(users, login):
print(f"User '{login}' already exists.")
sys.exit(1)
full_name = input("Full name: ").strip()
if not full_name:
print("Full name cannot be empty.")
sys.exit(1)
perms = input("Permissions (any combination of r, w, d): ").strip()
err = validate_permissions(perms)
if err:
print(f"Invalid permissions: {err}")
sys.exit(1)
password = prompt_password()
uid = next_uid(users)
users.append(
{
"login": login,
"password_hash": hash_password(password),
"id": uid,
"permissions": perms,
"full_name": full_name,
}
)
write_users(users)
log_action(f"ADD login={login} id={uid} permissions={perms} full_name='{full_name}'")
print(f"User '{login}' added (id={uid}).")
def cmd_edit(args: argparse.Namespace) -> None:
if args.label is not None:
require_root()
users = read_users()
user = find_user(users, args.login)
if not user:
print(f"User '{args.login}' not found.")
sys.exit(1)
changed: list[str] = []
if args.full_name is not None:
if not args.full_name:
print("Full name cannot be empty.")
sys.exit(1)
user["full_name"] = args.full_name
changed.append("full_name")
if args.permissions is not None:
err = validate_permissions(args.permissions)
if err:
print(f"Invalid permissions: {err}")
sys.exit(1)
user["permissions"] = args.permissions
changed.append("permissions")
if args.label is not None:
if args.label not in VALID_LABELS:
print(f"Invalid label {args.label!r}; allowed: 0, 1, 2")
sys.exit(1)
labels = read_subject_labels()
labels[args.login] = int(args.label)
write_subject_labels(labels)
changed.append("label")
if not changed:
print("Nothing to change. Use --full-name, --permissions, and/or --label.")
sys.exit(0)
if "full_name" in changed or "permissions" in changed:
write_users(users)
log_action(f"EDIT login={args.login} changed={','.join(changed)}")
print(f"User '{args.login}' updated: {', '.join(changed)}.")
def cmd_passwd(args: argparse.Namespace) -> None:
users = read_users()
user = find_user(users, args.login)
if not user:
print(f"User '{args.login}' not found.")
sys.exit(1)
password = prompt_password()
user["password_hash"] = hash_password(password)
write_users(users)
log_action(f"PASSWD login={args.login}")
print(f"Password for '{args.login}' updated.")
def cmd_delete(args: argparse.Namespace) -> None:
users = read_users()
if not find_user(users, args.login):
print(f"User '{args.login}' not found.")
sys.exit(1)
users = [u for u in users if u["login"] != args.login]
write_users(users)
log_action(f"DELETE login={args.login}")
print(f"User '{args.login}' deleted.")
def cmd_list(_args: argparse.Namespace) -> None:
users = read_users()
if not users:
print("No users.")
return
print(f"{'Login':<16} {'ID':<4} {'Perms':<6} Full Name")
print("-" * 52)
for u in users:
print(f"{u['login']:<16} {u['id']:<4} {u['permissions']:<6} {u['full_name']}")
def cmd_set_mode(args: argparse.Namespace) -> None:
require_root()
mode = args.mode.upper()
if mode not in ("BOTH", "DAC_ONLY", "MAC_ONLY"):
print(f"Invalid mode {args.mode!r}; allowed: BOTH, DAC_ONLY, MAC_ONLY")
sys.exit(1)
write_access_mode(mode)
log_action(f"SET_MODE mode={mode}")
print(f"Access mode set to {mode}.")
def cmd_show_mode(_args: argparse.Namespace) -> None:
require_root()
mode = read_access_mode()
print(f"Current access mode: {mode}")
def cmd_set_label(args: argparse.Namespace) -> None:
require_root()
if args.label not in VALID_LABELS:
print(f"Invalid label {args.label!r}; allowed: 0, 1, 2")
sys.exit(1)
path = args.path
if path.startswith("./"):
path = path[2:]
path = "/".join(p for p in path.split("/") if p)
labels = read_object_labels()
labels[path] = int(args.label)
write_object_labels(labels)
log_action(f"SET_LABEL path={path} label={args.label}")
print(f"Object label for '{path}' set to {args.label}.")
def main() -> None:
parser = argparse.ArgumentParser(description="User management utility for practice3")
sub = parser.add_subparsers(dest="command", required=True)
p_add = sub.add_parser("add", help="Add a new user")
p_add.add_argument("login", help="Username (login)")
p_add.set_defaults(func=cmd_add)
p_edit = sub.add_parser("edit", help="Edit an existing user")
p_edit.add_argument("login", help="Username to edit")
p_edit.add_argument("--full-name", dest="full_name", help="New full name")
p_edit.add_argument("--permissions", help="New permissions (e.g. rwd)")
p_edit.add_argument("--label", help="Security label (0, 1, 2) - root only")
p_edit.set_defaults(func=cmd_edit)
p_passwd = sub.add_parser("passwd", help="Change user password")
p_passwd.add_argument("login", help="Username")
p_passwd.set_defaults(func=cmd_passwd)
p_del = sub.add_parser("delete", help="Delete a user")
p_del.add_argument("login", help="Username to delete")
p_del.set_defaults(func=cmd_delete)
p_list = sub.add_parser("list", help="List all users")
p_list.set_defaults(func=cmd_list)
p_set_mode = sub.add_parser("set-mode", help="Set access mode (BOTH/DAC_ONLY/MAC_ONLY) - root only")
p_set_mode.add_argument("mode", help="BOTH, DAC_ONLY, or MAC_ONLY")
p_set_mode.set_defaults(func=cmd_set_mode)
p_show_mode = sub.add_parser("show-mode", help="Show current access mode - root only")
p_show_mode.set_defaults(func=cmd_show_mode)
p_set_label = sub.add_parser("set-label", help="Set object security label - root only")
p_set_label.add_argument("path", help="Path relative to confdata")
p_set_label.add_argument("label", help="0, 1, or 2")
p_set_label.set_defaults(func=cmd_set_label)
args = parser.parse_args()
args.func(args)
if __name__ == "__main__":
main()

536
lab4/README.md Normal file
View File

@@ -0,0 +1,536 @@
Сетевые адаптеры:
Adapter 1: NAT
для выхода в интернет (DNS, HTTP)
Adapter 2: Internal Network
имя: intnet
для связи с VM2
sudo vim /etc/netplan/01-netcfg.yaml
network:
version: 2
ethernets:
enp0s3: # NAT
dhcp4: true
enp0s8: # internal
addresses: [192.168.100.1/24]
network:
version: 2
ethernets:
enp0s3: # NAT
dhcp4: true
enp0s8: # internal
addresses: [192.168.100.2/24]
sudo chmod 600 /etc/netplan/01-netcfg.yaml
sudo netplan apply
sudo iptables -F
sudo iptables -X
sudo iptables -t nat -F
Ниже — учебная инструкция **только для настройки и проверки iptables**, без шагов про установку ОС, VirtualBox, netplan и утилит. Будем исходить из твоего текущего стенда:
* `firewall-host` — защищаемый хост, IP `192.168.100.1`
* `external-client` — внешний клиент, IP `192.168.100.2`
* у обеих машин есть NAT-интерфейс `enp0s3`
* внутренний интерфейс — `enp0s8`
* SSH к обеим машинам у тебя уже работает через проброс портов VirtualBox на `127.0.0.1:40001` и `127.0.0.1:40002`
Политика лабы: разрешить loopback, DNS, ping наружу, ping к защищаемому хосту только с одного IP, HTTP/HTTPS, а всё остальное запретить. Это прямо соответствует тексту задания.
---
# 2. Сначала разрешаем SSH, чтобы не отрезать себе доступ
Так как ты подключаешься к `firewall-host` по SSH, нужно **до включения блокировки** разрешить входящие SSH-подключения.
В твоём случае подключение приходит на саму Ubuntu-машину уже **после NAT VirtualBox**, то есть для Linux это обычный входящий TCP на порт `22`.
Выполни на `firewall-host`:
```bash
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
```
Пояснение:
* `-A INPUT` — добавить правило в конец цепочки `INPUT`, которая обрабатывает пакеты, входящие на этот хост.
* `-p tcp` — правило относится только к протоколу TCP.
* `--dport 22` — порт назначения 22, то есть SSH-сервер.
* `-j ACCEPT` — разрешить такой трафик.
Теперь разрешим ответы сервера по уже установленному SSH-соединению:
```bash
sudo iptables -A OUTPUT -p tcp --sport 22 -j ACCEPT
```
Пояснение:
* `-A OUTPUT` — добавить правило в цепочку `OUTPUT`, то есть для пакетов, исходящих с этого хоста.
* `-p tcp` — правило касается TCP.
* `--sport 22` — исходный порт 22; это ответы SSH-сервера клиенту.
* `-j ACCEPT` — разрешить.
Такой вариант рабочий, но в Linux обычно лучше использовать правило состояний соединений. Поэтому следующим шагом мы добавим его тоже.
---
# 3. Разрешаем уже установленные и связанные соединения
Это одно из самых важных правил. Оно позволяет не расписывать вручную каждый ответный пакет.
```bash
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
```
Пояснение:
* `-A INPUT` — правило для входящих пакетов.
* `-m conntrack` — подключить модуль отслеживания состояний соединений.
* `--ctstate ESTABLISHED,RELATED` — матчить пакеты:
* `ESTABLISHED` — относящиеся к уже установленному соединению;
* `RELATED` — связанные с уже существующим соединением.
* `-j ACCEPT` — разрешить.
И аналогично для исходящих:
```bash
sudo iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
```
Пояснение:
* здесь то же самое, но для исходящих пакетов.
Это правило особенно важно для:
* SSH-сессии, в которой ты уже сидишь;
* ответов от DNS-сервера;
* ответов от HTTP/HTTPS-серверов;
* ответов на исходящий ping (`echo-reply`).
**Порядок имеет значение.** Раз это правило стоит **раньше**, чем узкие правила вида «принять UDP/TCP с `--sport 53` или TCP с `--sport 80/443`» или «`echo-reply`», то **все ответные пакеты** по уже разрешённым исходящим запросам срабатывают здесь. Отдельные строки `INPUT` для `sport` DNS/HTTP(S) и для входящего `echo-reply` становятся **лишними**: до них очередь не дойдёт, а счётчики у таких правил останутся нулевыми.
На `INPUT` в учебной конфигурации **осмысленно оставить** явные разрешения только для **нового** входящего трафика, которое не описывается как `ESTABLISHED`/`RELATED`: SSH (если нужен), loopback и входящий `echo-request` только с разрешённого адреса (см. раздел 8).
---
# 4. Разрешаем loopback
Loopback — это локальное взаимодействие внутри самой ОС через интерфейс `lo`. По условию лабы его нужно разрешить.
```bash
sudo iptables -A INPUT -i lo -j ACCEPT
```
Пояснение:
* `-i lo` — входящий интерфейс `lo`, то есть loopback.
* `-j ACCEPT` — разрешить.
```bash
sudo iptables -A OUTPUT -o lo -j ACCEPT
```
Пояснение:
* `-o lo` — исходящий интерфейс `lo`.
* `-j ACCEPT` — разрешить.
---
# 5. Разрешаем DNS
По условию нужно разрешить взаимодействие с DNS-сервером. Обычно DNS-запросы идут по UDP на порт `53`. Иногда может использоваться и TCP 53, поэтому для учебной работы лучше разрешить оба варианта.
## UDP DNS
```bash
sudo iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
```
Пояснение:
* `-A OUTPUT` — правило для исходящих запросов.
* `-p udp` — DNS чаще всего использует UDP.
* `--dport 53` — порт назначения 53, стандартный порт DNS.
* `-j ACCEPT` — разрешить.
Входящие ответы DNS (UDP с `sport 53`) после этого правила пропускаются правилом `ESTABLISHED,RELATED` на `INPUT`; отдельная строка `INPUT --sport 53` не нужна и при типичном порядке правил всё равно не получила бы трафика.
## TCP DNS
```bash
sudo iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT
```
Пояснение:
* TCP используется реже, но может понадобиться для больших DNS-ответов или специальных случаев.
* входящие TCP-ответы от DNS идут через `ESTABLISHED,RELATED` на `INPUT`.
---
# 6. Разрешаем HTTP и HTTPS
По заданию нужно разрешить доступ к любым внешним серверам по HTTP/HTTPS.
## HTTP
```bash
sudo iptables -A OUTPUT -p tcp --dport 80 -j ACCEPT
```
Пояснение:
* `-p tcp` — HTTP работает по TCP.
* `--dport 80` — порт назначения 80, стандартный HTTP.
* правило разрешает открывать веб-страницы по HTTP.
Входящие ответы HTTP (и далее HTTPS) принимаются правилом `ESTABLISHED,RELATED` на `INPUT`; отдельное `INPUT --sport 80` не требуется.
## HTTPS
```bash
sudo iptables -A OUTPUT -p tcp --dport 443 -j ACCEPT
```
Пояснение:
* порт 443 — стандартный HTTPS.
Входящие ответы HTTPS обрабатываются на `INPUT` через `ESTABLISHED,RELATED`.
---
# 7. Разрешаем ping наружу
По заданию нужно разрешить использование `ping` для проверки достижимости любых компьютеров во внешней сети. Для `ping` используется протокол ICMP. Конкретно запрос — это `echo-request`, ответ — `echo-reply`.
```bash
sudo iptables -A OUTPUT -p icmp --icmp-type echo-request -j ACCEPT
```
Пояснение:
* `-p icmp` — правило для протокола ICMP.
* `--icmp-type echo-request` — ICMP-пакеты типа “эхо-запрос”, то есть сам ping-запрос.
* `-j ACCEPT` — разрешить отправку.
Ответы `echo-reply` на этот исходящий ping ядро относит к той же «сессии»; на `INPUT` они проходят через `ESTABLISHED,RELATED`. Отдельное правило `INPUT -p icmp --icmp-type echo-reply` не обязательно и при раннем conntrack дублирует его бесполезно.
---
# 8. Разрешаем ping к защищаемому хосту только с одного адреса
По заданию защищаемый хост должен отвечать на ping только от одного конкретного внешнего адреса. В твоём стенде таким адресом будет `192.168.100.2`, то есть `external-client`.
```bash
sudo iptables -A INPUT -p icmp --icmp-type echo-request -s 192.168.100.2 -d 192.168.100.1 -j ACCEPT
```
Пояснение:
* `-p icmp` — ICMP.
* `--icmp-type echo-request` — входящий ping-запрос.
* `-s 192.168.100.2` — источник должен быть именно `192.168.100.2`.
* `-d 192.168.100.1` — адрес назначения — защищаемый хост.
* `-j ACCEPT` — разрешить.
```bash
sudo iptables -A OUTPUT -p icmp --icmp-type echo-reply -s 192.168.100.1 -d 192.168.100.2 -j ACCEPT
```
Пояснение:
* это правило разрешает отправку ответа ping именно этому клиенту.
* `-s 192.168.100.1` — источник ответа, сам firewall-host.
* `-d 192.168.100.2` — получатель ответа, только разрешённый клиент.
---
# 9. Только теперь включаем политику DROP по умолчанию
Когда все разрешающие правила уже стоят, можно включить запрет всего остального.
```bash
sudo iptables -P INPUT DROP
```
Пояснение:
* `-P` — установить политику по умолчанию для цепочки.
* `INPUT` — цепочка входящих пакетов.
* `DROP` — все пакеты, которые не подошли ни под одно разрешающее правило, будут отбрасываться.
```bash
sudo iptables -P OUTPUT DROP
```
Пояснение:
* то же самое для исходящих пакетов.
```bash
sudo iptables -P FORWARD DROP
```
Пояснение:
* цепочка `FORWARD` нужна для транзитных пакетов через хост.
* в этой лабораторной маршрутизатор делать не требуется, поэтому безопасно оставить `DROP`.
---
# 10. Проверяем, что SSH не отвалился
Сразу после установки политик выполни:
```bash
sudo iptables -L -n -v --line-numbers
```
Пояснение:
* убедись, что правила SSH, conntrack, loopback, исходящие DNS/HTTP(S)/ICMP и узкое правило входящего ping с `192.168.100.2` стоят в списке.
* на `INPUT` основной рост счётчиков у «ответного» трафика обычно виден у строки `ESTABLISHED,RELATED`, а не у отдельных `--sport` (если ты их вообще не добавляешь).
Открой **вторую SSH-сессию** к `firewall-host`:
```bash
ssh -p 40001 arity@127.0.0.1
```
Пояснение:
* это контрольная проверка.
* пока первая сессия ещё жива, ты проверяешь, что новое подключение тоже проходит.
* если вторая сессия открылась, значит SSH точно не заблокирован.
---
# 11. Проверка правил по заданию
## Проверка DNS
На `firewall-host`:
```bash
nslookup example.com
```
Пояснение:
* `nslookup` отправляет DNS-запрос серверу имён.
* если команда возвращает IP-адрес сайта, значит DNS разрешён.
## Проверка HTTP
```bash
curl http://example.com
```
Пояснение:
* `curl` делает HTTP-запрос к веб-серверу.
* если приходит HTML-ответ, правило HTTP работает.
## Проверка HTTPS
```bash
curl https://example.com
```
Пояснение:
* то же самое, но по HTTPS на порт 443.
## Проверка ping наружу
```bash
ping -c 4 8.8.8.8
```
Пояснение:
* `ping` — утилита проверки достижимости узла.
* `-c 4` — отправить только 4 запроса, а не бесконечно.
* `8.8.8.8` — внешний IP-адрес.
* если ответы приходят, исходящий ping разрешён.
## Проверка ping к защищаемому хосту с разрешённого адреса
На `external-client`:
```bash
ping -c 4 192.168.100.1
```
Пояснение:
* клиент `192.168.100.2` должен успешно пинговать `firewall-host`, потому что именно этот источник разрешён.
## Проверка блокировки лишнего трафика
Надёжный вариант — **TCP на порт, который не разрешён политикой**, на второй ВМ в `intnet` (у тебя это `external-client`).
На `external-client` подними слушатель на порт, например `8080`:
```bash
python3 -m http.server 8080
```
На `firewall-host` попробуй подключиться:
```bash
timeout 5 nc -vz 192.168.100.2 8080
```
Пояснение:
* исходящий TCP на `192.168.100.2:8080` не попадает под разрешённые `dport 53`/`80`/`443`, поэтому при политике `OUTPUT DROP` соединение не устанавливается (обычно таймаут).
* проверка к `example.com:22` для отчёта часто бессмысленна: порт 22 у публичных имён часто закрыт независимо от твоего МЭ.
---
# 12. Контроль трафика через tcpdump
По заданию нужно показать прохождение пакетов через `tcpdump`.
Общий просмотр трафика:
```bash
sudo tcpdump -i any -n
```
Пояснение:
* `tcpdump` — сниффер пакетов.
* `-i any` — слушать все интерфейсы сразу.
* `-n` — не преобразовывать адреса в имена.
Просмотр только ICMP:
```bash
sudo tcpdump -i any -n icmp
```
Пояснение:
* фильтр `icmp` покажет только ping-трафик.
Просмотр DNS:
```bash
sudo tcpdump -i any -n port 53
```
Пояснение:
* `port 53` — показать DNS-пакеты.
Просмотр HTTP/HTTPS:
```bash
sudo tcpdump -i any -n 'tcp port 80 or tcp port 443'
```
Пояснение:
* кавычки нужны, чтобы shell корректно передал выражение целиком.
* `or` — логическое “или”.
* выражение показывает трафик к HTTP и HTTPS.
Просмотр SSH:
```bash
sudo tcpdump -i any -n tcp port 22
```
Пояснение:
* поможет убедиться, что SSH-пакеты действительно проходят через фильтр.
---
# 13. Сохранение правил
Когда убедишься, что всё работает, сохрани конфигурацию:
```bash
sudo netfilter-persistent save
```
Пояснение:
* `netfilter-persistent` сохраняет текущие правила iptables, чтобы они восстановились после перезагрузки.
Можно дополнительно сохранить в файл:
```bash
sudo iptables-save > ~/iptables-lab4.rules
```
Пояснение:
* это текстовый дамп всех правил.
* удобно приложить к отчёту или использовать для восстановления.
---
# 14. Готовый набор команд в правильном порядке
Ниже — тот же порядок, но компактным блоком, чтобы ты мог выполнять по шагам:
```bash
sudo iptables-save > ~/iptables-before-lab.rules
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
sudo iptables -A OUTPUT -p tcp --sport 22 -j ACCEPT
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A INPUT -i lo -j ACCEPT
sudo iptables -A OUTPUT -o lo -j ACCEPT
sudo iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
sudo iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT
sudo iptables -A OUTPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -A OUTPUT -p tcp --dport 443 -j ACCEPT
sudo iptables -A OUTPUT -p icmp --icmp-type echo-request -j ACCEPT
sudo iptables -A INPUT -p icmp --icmp-type echo-request -s 192.168.100.2 -d 192.168.100.1 -j ACCEPT
sudo iptables -A OUTPUT -p icmp --icmp-type echo-reply -s 192.168.100.1 -d 192.168.100.2 -j ACCEPT
sudo iptables -P INPUT DROP
sudo iptables -P OUTPUT DROP
sudo iptables -P FORWARD DROP
```
---
# 16. Что написать в учебном смысле про SSH как “дополнение”
Можно формулировать так:
> В базовом задании SSH не входит в перечень разрешённого трафика, однако для сохранения удалённого доступа к стенду было добавлено дополнительное правило, разрешающее входящие TCP-соединения на порт 22 защищаемого хоста. Правило было установлено до включения политик DROP по умолчанию, чтобы не потерять административный доступ к системе.
Это хорошо звучит и по сути верно.

69
lab4/lab4.md Normal file
View File

@@ -0,0 +1,69 @@
# Защита информации, 2026
## Практическая работа №4
**по дисциплине «Защита информации»**
**Тема работы:** «Межсетевое экранирование»
**Преподаватель:** Силиненко А.В.
**Email:** a_silinenko@mail.ru
## 1. Цель работы
Получение базовых знаний по настройке межсетевого экрана (МЭ) в ОС Linux/MacOS.
## 2. Задачи практической работы
### 2.1.
Изучение МЭ iptables или любого другого, обеспечивающего выполнение задания.
### 2.2.
Реализация заданной политики доступа.
## 3. Ход практической работы
### 3.1.
Установить или убедиться в наличии iptables.
### 3.2.
Изучить МЭ iptables, в том числе:
- установка МЭ;
- принципы и порядок обработки пакетов, основные цепочки (таблицы) обработки;
- действия по умолчанию;
- возможные параметры правил фильтрации.
### 3.3.
Изучить команды вывода таблиц фильтрации, добавления, редактирования и удаления правил фильтрации.
### 3.4.
Реализовать следующую политику доступа:
- разрешить локальное взаимодействие через интерфейс loopback;
- разрешить взаимодействие с DNS-сервером;
- разрешить использование утилиты ping для проверки достижимости компьютеров с любыми IP-адресами во внешней сети;
- разрешить использование утилиты ping для проверки достижимости защищаемого хоста только с конкретного адреса внешней сети;
- разрешить доступ по протоколам HTTP/HTTPS к любым внешним серверам;
- блокировать все пакеты, не удовлетворяющие указанным выше условиям.
Правила политики доступа должны содержать (там, где это уместно):
- IP-адрес (сеть) источника;
- IP-адрес (сеть) приемника;
- транспортный протокол;
- для протоколов TCP и UDP: порт приемника;
- для протокола ICMP: тип и код сообщения.
### 3.5.
Произвести проверку корректности реализации заданной политики, используя программы ping, nslookup (или аналоги), web-браузер.
Также убедиться, что весь трафик, кроме разрешенного, блокируется.
### 3.6.
Проконтролировать прохождение пакетов утилитой tcpdump.
## 4. Требования к отчету
### 4.1.
В разделе отчета, посвященному данной работе, должны быть приведены:
- актуальность темы работы в контексте курса;
- цели и задачи работы;
- схема стенда, в т.ч. защищаемый компьютер с МЭ, внешняя сеть, DNS-сервер(а), IP-адреса хостов;
- принципы обработки пакетов в цепочках (таблицах) iptables;
- примеры команд добавления правил фильтрации;
- набор правил фильтрации, реализующий заданную политику доступа;
- проверку реализованной политики демонстрацию примеров пропуска разрешенного трафика и блокировки запрещенного;
- пример контроля трафика (вывод tcpdump) при проверках реализованной политики;
- выводы по проделанной работе.

27
lab4/stand.mmd Normal file
View File

@@ -0,0 +1,27 @@
graph TB
subgraph host["Хост-машина"]
subgraph fw["firewall-host (защищаемый хост, iptables)"]
fw_lo["lo: 127.0.0.1/8"]
fw_nat["enp0s3 &lpar;NAT&rpar;<br/>10.0.2.15/24"]
fw_int["enp0s8 &lpar;Internal&rpar;<br/>192.168.100.1/24"]
end
subgraph ec["external-client (внешний клиент)"]
ec_nat["enp0s3 &lpar;NAT&rpar;<br/>10.0.2.15/24"]
ec_int["enp0s8 &lpar;Internal&rpar;<br/>192.168.100.2/24"]
end
end
fw_int <--->|"Internal Network &lpar;intnet&rpar;<br/>192.168.100.0/24"| ec_int
inet["Интернет<br/>&lpar;DNS: 10.0.2.3, HTTP/HTTPS,<br/>ICMP: 8.8.8.8 и др.&rpar;"]
fw_nat -->|"NAT"| inet
style fw fill:#e8f4e8,stroke:#2d7d2d,stroke-width:2px
style ec fill:#e8e8f4,stroke:#2d2d7d,stroke-width:2px
style host fill:#f9f9f9,stroke:#999,stroke-width:1px
style inet fill:#fff3e0,stroke:#e65100,stroke-width:2px
style fw_lo fill:#fff,stroke:#666
style fw_nat fill:#fff,stroke:#666
style fw_int fill:#fff,stroke:#666
style ec_nat fill:#fff,stroke:#666
style ec_int fill:#fff,stroke:#666

2
lab5/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
key.txt
*.pyc

1
lab5/.python-version Normal file
View File

@@ -0,0 +1 @@
3.14

76
lab5/README.md Normal file
View File

@@ -0,0 +1,76 @@
# Практическая работа №5 — Конфиденциальный обмен сообщениями
Клиент-серверное приложение для обмена сообщениями по TCP с поддержкой
шифрования 3DES-CBC (с затравкой; ключ сессии из SHA-256 от ключа файла и соли),
контроля целостности сообщения **MD5** (по варианту, как в работе №2) и опциональной
аутентификации отправителя **HMAC-SHA256**.
## Установка
```bash
uv sync
```
## Ключ шифрования
Ключ хранится в текстовом файле (не менее 32 символов). Права на файл — только
чтение владельцем (`600`). Программа проверяет права при запуске и отказывается
работать, если файл доступен группе или другим пользователям.
```bash
openssl rand -hex 32 > key.txt
chmod 600 key.txt
```
## Запуск
### Сервер
```bash
uv run main.py server --port 9000 # без шифрования
uv run main.py server --port 9000 --encrypt # 3DES-CBC
uv run main.py server --port 9000 --encrypt --integrity # + MD5 digest
uv run main.py server --port 9000 --encrypt --integrity --test-integrity # отправка с повреждённым хэшем
uv run main.py server --port 9000 --encrypt --hmac # 3DES + HMAC-SHA256
uv run main.py server --port 9000 --hmac # только HMAC (без шифрования)
uv run main.py server --port 9000 --key /path/to/key.txt --encrypt # другой файл ключа
```
### Клиент
```bash
uv run main.py client 127.0.0.1 9000
uv run main.py client 127.0.0.1 9000 --encrypt
uv run main.py client 127.0.0.1 9000 --encrypt --integrity
uv run main.py client 127.0.0.1 9000 --encrypt --integrity --test-integrity
uv run main.py client 127.0.0.1 9000 --encrypt --hmac
uv run main.py client 127.0.0.1 9000 --encrypt --hmac --test-integrity
```
## Режимы работы
| Флаг | Описание |
|------|----------|
| *(без флагов)* | Открытый текст, без проверки целостности |
| `--encrypt` | Шифрование 3DES-CBC с затравкой (соль + IV генерируются случайно для каждого сообщения) |
| `--integrity` | Контроль целостности — MD5-хэш открытого текста (по индивидуальному заданию) |
| `--hmac` | HMAC-SHA256 — контроль целостности + аутентификация отправителя (требует ключ, несовместим с `--integrity`) |
| `--test-integrity` | Режим тестирования: отправляется заведомо некорректный хэш/HMAC (требует `--integrity` или `--hmac`) |
## Анализ трафика с помощью tcpdump
Для наблюдения за передаваемыми данными удобно использовать `tcpdump`.
При работе на одной машине (loopback):
```bash
sudo tcpdump -i lo -X -n tcp port 9000
```
При работе по сети (замените `eth0` на имя сетевого интерфейса):
```bash
sudo tcpdump -i eth0 -X -n tcp port 9000
```
- **Без шифрования** — в дампе видны сообщения открытым текстом (JSON с полем `"data": "текст сообщения"`).
- **С шифрованием** — в дампе видны только шифротекст, соль и IV в hex; читаемый текст отсутствует.

68
lab5/lab5.md Normal file
View File

@@ -0,0 +1,68 @@
# Практическая работа №5
по дисциплине «Защита информации»
**Тема работы:** «Разработка клиент-серверного приложения для конфиденциального обмена сообщениями»
**Преподаватель:** Силиненко А.В.
**Email:** a_silinenko@mail.ru
## 1. Цель и задачи работы
### 1.1. Цель работы
Разработка клиент-серверного приложения для конфиденциального обмена сообщениями с использованием шифрования и контроля целостности.
### 1.2. Задачи работы
- получение базовых знаний по использованию криптографических функций библиотеки `crypto` в ОС Linux или другой библиотеки, обеспечивающей реализацию требований задания;
- разработка клиент-серверного приложения для обмена шифрованными сообщениями с контролем целостности;
- проверка работы приложения в различных режимах использования с помощью программы анализа трафика.
## 2. Требования к приложению
### 2.1. Общие требования
Разработать приложение типа «клиент-сервер», позволяющее обмениваться короткими сообщениями между клиентской и серверной частями приложения с обеспечением шифрования и контроля целостности.
### 2.2. Общие требования
- протокол взаимодействия: TCP;
- длина сообщения: не более 80 символов;
- полнодуплексный обмен сообщениями: возможность одновременного приема и передачи сообщений;
- IP-адрес и порт серверной части передается клиентской части приложения в качестве параметров запуска в командной строке;
- приложение должно быть способно функционировать как при запуске клиентской и серверной частей приложения на одном компьютере, так и на разных сетевых узлах;
- язык написания программы: любой.
### 2.3. Требования к функции шифрования
- приложение должно поддерживать функцию шифрования сообщений на основе симметричного шифрования с использованием библиотеки `crypto`, входящей в API `openssl`, или другой аналогичной библиотеки;
- алгоритм симметричного шифрования: согласно индивидуальному заданию;
В моём варианте это 3DES
- ключ шифрования длиной не менее 32 символов задается в файле;
- права доступа к файлу: чтение только владельцем;
- использование опции шифрования задается ключом командной строки при запуске программы.
### 2.4. Требования к функции контроля целостности
- приложение должно поддерживать функцию контроля целостности на основе подсчета хэш-значения передаваемого сообщения;
- алгоритм вычисления хэш-значения - в соответствии с индивидуальным заданием (см. практическую работу №2);
- использование опции контроля целостности задается ключом командной строки при запуске программы;
- предусмотреть режим тестирования, в котором при включенной опции контроля целостности отправляются сообщения с некорректным хэш-значением.
### 2.5. Необязательные требования
- реализовать шифрование с «затравкой» («солью»), обеспечивающей разные шифрованные сообщения при одном и том же открытом тексте;
- реализовать для разработанного приложения опцию выработки и использования сессионного ключа на основе алгоритма Диффи-Хеллмана вместо заранее заданного ключа.
## 3. Требования к отчету
### 3.1. В разделе отчета, посвященном данной работе, должны быть приведены
- актуальность темы работы в контексте курса;
- цели и задачи работы;
- краткое описание особенностей используемого алгоритма симметричного шифрования, а также шифрования «с затравкой» и алгоритма Диффи-Хеллмана, если реализованы соответствующие требования;
- команды компиляции и сборки исполняемых модулей приложения;
- описание процедуры проверки работы приложения с примерами, включая выводы программы анализа трафика, подтверждающие реализацию требований передачи сообщений без шифрования, с шифрованием, с шифрованием и контролем целостности;
- описание проверки опции контроля целостности, в которой отправляются сообщения с некорректным хэш-значением;
- описание проверки шифрования «с затравкой» и алгоритма Диффи-Хеллмана, если реализованы соответствующие требования;
- текст разработанного приложения (клиентской и серверной частей) с комментариями - в приложении к отчету;
- выводы по проделанной работе.

367
lab5/main.py Normal file
View File

@@ -0,0 +1,367 @@
from __future__ import annotations
import argparse
import hashlib
import hmac
import json
import os
import socket
import stat
import struct
import sys
import threading
from cryptography.hazmat.decrepit.ciphers.algorithms import TripleDES
from cryptography.hazmat.primitives.ciphers import Cipher, modes
from cryptography.hazmat.primitives import padding as sym_padding
MAX_MSG_LEN = 80
DEFAULT_KEY_FILE = "key.txt"
def check_key_permissions(path: str) -> None:
st = os.stat(path)
mode = st.st_mode
bad = stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IWOTH | stat.S_IXOTH
if mode & bad:
current = oct(mode & 0o777)
print(f"[!] Key file permissions too open ({current}): {path}")
print(f" Run: chmod 600 {path}")
sys.exit(1)
def load_key(path: str) -> str:
if not os.path.isfile(path):
print(f"[!] Key file not found: {path}")
sys.exit(1)
check_key_permissions(path)
with open(path) as f:
key = f.read().strip()
if len(key) < 32:
print(f"[!] Key must be at least 32 characters (got {len(key)})")
sys.exit(1)
return key
def derive_3des_key(file_key: str, salt: bytes) -> bytes:
return hashlib.sha256(file_key.encode() + salt).digest()[:24]
def encrypt_message(plaintext: str, file_key: str) -> tuple[bytes, bytes, bytes]:
salt = os.urandom(16)
key = derive_3des_key(file_key, salt)
iv = os.urandom(8)
padder = sym_padding.PKCS7(64).padder()
padded = padder.update(plaintext.encode()) + padder.finalize()
encryptor = Cipher(TripleDES(key), modes.CBC(iv)).encryptor()
ct = encryptor.update(padded) + encryptor.finalize()
return salt, iv, ct
def decrypt_message(salt: bytes, iv: bytes, ct: bytes, file_key: str) -> str:
key = derive_3des_key(file_key, salt)
decryptor = Cipher(TripleDES(key), modes.CBC(iv)).decryptor()
padded = decryptor.update(ct) + decryptor.finalize()
unpadder = sym_padding.PKCS7(64).unpadder()
plaintext = unpadder.update(padded) + unpadder.finalize()
return plaintext.decode()
def compute_hash(text: str) -> str:
return hashlib.md5(text.encode(), usedforsecurity=False).hexdigest()
def compute_hmac(file_key: str, text: str, salt: bytes) -> str:
return hmac.new(file_key.encode(), text.encode() + salt, hashlib.sha256).hexdigest()
def recv_exactly(sock: socket.socket, n: int) -> bytes | None:
buf = b""
while len(buf) < n:
chunk = sock.recv(n - len(buf))
if not chunk:
return None
buf += chunk
return buf
def send_packet(sock: socket.socket, data: bytes) -> None:
sock.sendall(struct.pack("!I", len(data)) + data)
def recv_packet(sock: socket.socket) -> dict | None:
header = recv_exactly(sock, 4)
if header is None:
return None
length = struct.unpack("!I", header)[0]
payload = recv_exactly(sock, length)
if payload is None:
return None
return json.loads(payload.decode())
def do_send(
sock: socket.socket,
text: str,
file_key: str | None,
use_encrypt: bool,
use_integrity: bool,
use_hmac: bool,
test_integrity: bool,
) -> None:
msg: dict = {}
print(f" [TX] plaintext: {text}")
enc_salt: bytes | None = None
if use_encrypt:
assert file_key is not None
salt, iv, ct = encrypt_message(text, file_key)
enc_salt = salt
derived_hex = derive_3des_key(file_key, salt).hex()
msg["encrypted"] = True
msg["salt"] = salt.hex()
msg["iv"] = iv.hex()
msg["data"] = ct.hex()
print(f" [TX] salt: {salt.hex()}")
print(f" [TX] 3DES key: {derived_hex}")
print(f" [TX] IV: {iv.hex()}")
print(f" [TX] ciphertext: {ct.hex()}")
else:
msg["encrypted"] = False
msg["data"] = text
if use_integrity:
h = compute_hash(text)
if test_integrity:
h = hashlib.md5(b"CORRUPTED_" + os.urandom(4), usedforsecurity=False).hexdigest()
print(f" [TX] MD5: {h} (CORRUPTED!)")
else:
print(f" [TX] MD5: {h}")
msg["hash"] = h
if use_hmac:
assert file_key is not None
hmac_salt = enc_salt if enc_salt is not None else os.urandom(16)
h = compute_hmac(file_key, text, hmac_salt)
msg["hmac_salt"] = hmac_salt.hex()
if test_integrity:
h = hmac.new(b"WRONG_KEY", os.urandom(8), hashlib.sha256).hexdigest()
print(f" [TX] HMAC salt: {hmac_salt.hex()}")
print(f" [TX] HMAC-256: {h} (CORRUPTED!)")
else:
print(f" [TX] HMAC salt: {hmac_salt.hex()}")
print(f" [TX] HMAC-256: {h}")
msg["hmac"] = h
send_packet(sock, json.dumps(msg).encode())
print()
def do_recv(
msg: dict,
file_key: str | None,
) -> str | None:
encrypted = msg.get("encrypted", False)
if encrypted:
salt = bytes.fromhex(msg["salt"])
iv = bytes.fromhex(msg["iv"])
ct = bytes.fromhex(msg["data"])
print(f" [RX] ciphertext: {ct.hex()}")
print(f" [RX] salt: {salt.hex()}")
print(f" [RX] IV: {iv.hex()}")
if file_key is None:
print(" [RX] ERROR: message is encrypted but no key loaded")
return None
derived_hex = derive_3des_key(file_key, salt).hex()
print(f" [RX] 3DES key: {derived_hex}")
try:
text = decrypt_message(salt, iv, ct, file_key)
except Exception as e:
print(f" [RX] decryption failed: {e}")
return None
print(f" [RX] decrypted: {text}")
else:
text = msg["data"]
print(f" [RX] plaintext: {text}")
if "hash" in msg:
received_hash = msg["hash"]
computed_hash = compute_hash(text)
ok = received_hash == computed_hash
print(f" [RX] recv hash: {received_hash}")
print(f" [RX] calc hash: {computed_hash}")
if ok:
print(" [RX] integrity: OK")
else:
print(" [RX] integrity: FAIL - message may have been tampered with!")
if "hmac" in msg:
hmac_salt = bytes.fromhex(msg["hmac_salt"])
received_hmac = msg["hmac"]
if file_key is None:
print(" [RX] ERROR: HMAC present but no key loaded")
else:
computed_h = compute_hmac(file_key, text, hmac_salt)
ok = hmac.compare_digest(received_hmac, computed_h)
print(f" [RX] HMAC salt: {hmac_salt.hex()}")
print(f" [RX] recv HMAC: {received_hmac}")
print(f" [RX] calc HMAC: {computed_h}")
if ok:
print(" [RX] HMAC auth: OK - sender verified")
else:
print(" [RX] HMAC auth: FAIL - sender NOT verified!")
print()
return text
def receiver_thread(
sock: socket.socket,
file_key: str | None,
stop_event: threading.Event,
) -> None:
try:
while not stop_event.is_set():
msg = recv_packet(sock)
if msg is None:
print("\n[*] Connection closed by remote side.")
stop_event.set()
break
do_recv(msg, file_key)
except OSError:
if not stop_event.is_set():
print("\n[*] Connection lost.")
stop_event.set()
def sender_thread(
sock: socket.socket,
file_key: str | None,
use_encrypt: bool,
use_integrity: bool,
use_hmac: bool,
test_integrity: bool,
stop_event: threading.Event,
) -> None:
try:
while not stop_event.is_set():
try:
text = input()
except EOFError:
stop_event.set()
break
if stop_event.is_set():
break
if len(text) > MAX_MSG_LEN:
print(f" [!] Message too long ({len(text)} > {MAX_MSG_LEN}), truncated.")
text = text[:MAX_MSG_LEN]
do_send(sock, text, file_key, use_encrypt, use_integrity, use_hmac, test_integrity)
except OSError:
if not stop_event.is_set():
stop_event.set()
def run_session(
sock: socket.socket,
addr: tuple,
file_key: str | None,
use_encrypt: bool,
use_integrity: bool,
use_hmac: bool,
test_integrity: bool,
) -> None:
flags = []
if use_encrypt:
flags.append("3DES")
if use_integrity:
flags.append("MD5")
if use_hmac:
flags.append("HMAC-SHA256")
if test_integrity:
flags.append("test-integrity")
mode_str = ", ".join(flags) if flags else "plaintext"
print(f"[*] Session with {addr[0]}:{addr[1]} [{mode_str}]")
print("[*] Type messages and press Enter to send. Ctrl+C / Ctrl+D to quit.\n")
stop = threading.Event()
rx = threading.Thread(target=receiver_thread, args=(sock, file_key, stop), daemon=True)
tx = threading.Thread(target=sender_thread, args=(sock, file_key, use_encrypt, use_integrity, use_hmac, test_integrity, stop), daemon=True)
rx.start()
tx.start()
try:
while not stop.is_set():
stop.wait(0.5)
except KeyboardInterrupt:
print("\n[*] Interrupted.")
stop.set()
sock.close()
def run_server(args: argparse.Namespace) -> None:
file_key = load_key(args.key) if (args.encrypt or args.hmac) else None
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind(("0.0.0.0", args.port))
srv.listen(1)
print(f"[*] Listening on 0.0.0.0:{args.port} ...")
conn, addr = srv.accept()
print(f"[*] Client connected: {addr[0]}:{addr[1]}")
srv.close()
run_session(conn, addr, file_key, args.encrypt, args.integrity, args.hmac, args.test_integrity)
def run_client(args: argparse.Namespace) -> None:
file_key = load_key(args.key) if (args.encrypt or args.hmac) else None
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((args.host, args.port))
print(f"[*] Connected to {args.host}:{args.port}")
run_session(sock, (args.host, args.port), file_key, args.encrypt, args.integrity, args.hmac, args.test_integrity)
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="Encrypted messaging (3DES-CBC; integrity MD5 per assignment; optional HMAC-SHA256)"
)
sub = parser.add_subparsers(dest="role", required=True)
for name, sp in [("server", sub.add_parser("server", help="Start server")),
("client", sub.add_parser("client", help="Start client"))]:
if name == "server":
sp.add_argument("--port", type=int, required=True)
else:
sp.add_argument("host")
sp.add_argument("port", type=int)
sp.add_argument("--encrypt", action="store_true", help="Enable 3DES-CBC encryption")
sp.add_argument("--integrity", action="store_true", help="MD5 integrity hash (variant / lab 2)")
sp.add_argument("--hmac", action="store_true", help="HMAC-SHA256 integrity + authentication")
sp.add_argument("--test-integrity", action="store_true", help="Send corrupted hash/HMAC")
sp.add_argument("--key", default=DEFAULT_KEY_FILE, help="Path to key file (default: key.txt)")
return parser
def main() -> None:
args = build_parser().parse_args()
if args.integrity and args.hmac:
print("[!] --integrity and --hmac are mutually exclusive")
sys.exit(1)
if args.test_integrity and not (args.integrity or args.hmac):
print("[!] --test-integrity requires --integrity or --hmac")
sys.exit(1)
if args.role == "server":
run_server(args)
else:
run_client(args)
if __name__ == "__main__":
main()

9
lab5/pyproject.toml Normal file
View File

@@ -0,0 +1,9 @@
[project]
name = "lab5"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.14"
dependencies = [
"cryptography>=46.0.6",
]

109
lab5/uv.lock generated Normal file
View File

@@ -0,0 +1,109 @@
version = 1
revision = 3
requires-python = ">=3.14"
[[package]]
name = "cffi"
version = "2.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pycparser", marker = "implementation_name != 'PyPy'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" },
{ url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" },
{ url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" },
{ url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" },
{ url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" },
{ url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" },
{ url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" },
{ url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" },
{ url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" },
{ url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" },
{ url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" },
{ url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" },
{ url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" },
{ url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" },
{ url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" },
{ url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" },
{ url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" },
{ url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" },
{ url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" },
{ url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" },
{ url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" },
{ url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" },
]
[[package]]
name = "cryptography"
version = "46.0.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a4/ba/04b1bd4218cbc58dc90ce967106d51582371b898690f3ae0402876cc4f34/cryptography-46.0.6.tar.gz", hash = "sha256:27550628a518c5c6c903d84f637fbecf287f6cb9ced3804838a1295dc1fd0759", size = 750542, upload-time = "2026-03-25T23:34:53.396Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/47/23/9285e15e3bc57325b0a72e592921983a701efc1ee8f91c06c5f0235d86d9/cryptography-46.0.6-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:64235194bad039a10bb6d2d930ab3323baaec67e2ce36215fd0952fad0930ca8", size = 7176401, upload-time = "2026-03-25T23:33:22.096Z" },
{ url = "https://files.pythonhosted.org/packages/60/f8/e61f8f13950ab6195b31913b42d39f0f9afc7d93f76710f299b5ec286ae6/cryptography-46.0.6-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:26031f1e5ca62fcb9d1fcb34b2b60b390d1aacaa15dc8b895a9ed00968b97b30", size = 4275275, upload-time = "2026-03-25T23:33:23.844Z" },
{ url = "https://files.pythonhosted.org/packages/19/69/732a736d12c2631e140be2348b4ad3d226302df63ef64d30dfdb8db7ad1c/cryptography-46.0.6-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9a693028b9cbe51b5a1136232ee8f2bc242e4e19d456ded3fa7c86e43c713b4a", size = 4425320, upload-time = "2026-03-25T23:33:25.703Z" },
{ url = "https://files.pythonhosted.org/packages/d4/12/123be7292674abf76b21ac1fc0e1af50661f0e5b8f0ec8285faac18eb99e/cryptography-46.0.6-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:67177e8a9f421aa2d3a170c3e56eca4e0128883cf52a071a7cbf53297f18b175", size = 4278082, upload-time = "2026-03-25T23:33:27.423Z" },
{ url = "https://files.pythonhosted.org/packages/5b/ba/d5e27f8d68c24951b0a484924a84c7cdaed7502bac9f18601cd357f8b1d2/cryptography-46.0.6-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:d9528b535a6c4f8ff37847144b8986a9a143585f0540fbcb1a98115b543aa463", size = 4926514, upload-time = "2026-03-25T23:33:29.206Z" },
{ url = "https://files.pythonhosted.org/packages/34/71/1ea5a7352ae516d5512d17babe7e1b87d9db5150b21f794b1377eac1edc0/cryptography-46.0.6-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:22259338084d6ae497a19bae5d4c66b7ca1387d3264d1c2c0e72d9e9b6a77b97", size = 4457766, upload-time = "2026-03-25T23:33:30.834Z" },
{ url = "https://files.pythonhosted.org/packages/01/59/562be1e653accee4fdad92c7a2e88fced26b3fdfce144047519bbebc299e/cryptography-46.0.6-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:760997a4b950ff00d418398ad73fbc91aa2894b5c1db7ccb45b4f68b42a63b3c", size = 3986535, upload-time = "2026-03-25T23:33:33.02Z" },
{ url = "https://files.pythonhosted.org/packages/d6/8b/b1ebfeb788bf4624d36e45ed2662b8bd43a05ff62157093c1539c1288a18/cryptography-46.0.6-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:3dfa6567f2e9e4c5dceb8ccb5a708158a2a871052fa75c8b78cb0977063f1507", size = 4277618, upload-time = "2026-03-25T23:33:34.567Z" },
{ url = "https://files.pythonhosted.org/packages/dd/52/a005f8eabdb28df57c20f84c44d397a755782d6ff6d455f05baa2785bd91/cryptography-46.0.6-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:cdcd3edcbc5d55757e5f5f3d330dd00007ae463a7e7aa5bf132d1f22a4b62b19", size = 4890802, upload-time = "2026-03-25T23:33:37.034Z" },
{ url = "https://files.pythonhosted.org/packages/ec/4d/8e7d7245c79c617d08724e2efa397737715ca0ec830ecb3c91e547302555/cryptography-46.0.6-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:d4e4aadb7fc1f88687f47ca20bb7227981b03afaae69287029da08096853b738", size = 4457425, upload-time = "2026-03-25T23:33:38.904Z" },
{ url = "https://files.pythonhosted.org/packages/1d/5c/f6c3596a1430cec6f949085f0e1a970638d76f81c3ea56d93d564d04c340/cryptography-46.0.6-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2b417edbe8877cda9022dde3a008e2deb50be9c407eef034aeeb3a8b11d9db3c", size = 4405530, upload-time = "2026-03-25T23:33:40.842Z" },
{ url = "https://files.pythonhosted.org/packages/7e/c9/9f9cea13ee2dbde070424e0c4f621c091a91ffcc504ffea5e74f0e1daeff/cryptography-46.0.6-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:380343e0653b1c9d7e1f55b52aaa2dbb2fdf2730088d48c43ca1c7c0abb7cc2f", size = 4667896, upload-time = "2026-03-25T23:33:42.781Z" },
{ url = "https://files.pythonhosted.org/packages/ad/b5/1895bc0821226f129bc74d00eccfc6a5969e2028f8617c09790bf89c185e/cryptography-46.0.6-cp311-abi3-win32.whl", hash = "sha256:bcb87663e1f7b075e48c3be3ecb5f0b46c8fc50b50a97cf264e7f60242dca3f2", size = 3026348, upload-time = "2026-03-25T23:33:45.021Z" },
{ url = "https://files.pythonhosted.org/packages/c3/f8/c9bcbf0d3e6ad288b9d9aa0b1dee04b063d19e8c4f871855a03ab3a297ab/cryptography-46.0.6-cp311-abi3-win_amd64.whl", hash = "sha256:6739d56300662c468fddb0e5e291f9b4d084bead381667b9e654c7dd81705124", size = 3483896, upload-time = "2026-03-25T23:33:46.649Z" },
{ url = "https://files.pythonhosted.org/packages/01/41/3a578f7fd5c70611c0aacba52cd13cb364a5dee895a5c1d467208a9380b0/cryptography-46.0.6-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:2ef9e69886cbb137c2aef9772c2e7138dc581fad4fcbcf13cc181eb5a3ab6275", size = 7117147, upload-time = "2026-03-25T23:33:48.249Z" },
{ url = "https://files.pythonhosted.org/packages/fa/87/887f35a6fca9dde90cad08e0de0c89263a8e59b2d2ff904fd9fcd8025b6f/cryptography-46.0.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7f417f034f91dcec1cb6c5c35b07cdbb2ef262557f701b4ecd803ee8cefed4f4", size = 4266221, upload-time = "2026-03-25T23:33:49.874Z" },
{ url = "https://files.pythonhosted.org/packages/aa/a8/0a90c4f0b0871e0e3d1ed126aed101328a8a57fd9fd17f00fb67e82a51ca/cryptography-46.0.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d24c13369e856b94892a89ddf70b332e0b70ad4a5c43cf3e9cb71d6d7ffa1f7b", size = 4408952, upload-time = "2026-03-25T23:33:52.128Z" },
{ url = "https://files.pythonhosted.org/packages/16/0b/b239701eb946523e4e9f329336e4ff32b1247e109cbab32d1a7b61da8ed7/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:aad75154a7ac9039936d50cf431719a2f8d4ed3d3c277ac03f3339ded1a5e707", size = 4270141, upload-time = "2026-03-25T23:33:54.11Z" },
{ url = "https://files.pythonhosted.org/packages/0f/a8/976acdd4f0f30df7b25605f4b9d3d89295351665c2091d18224f7ad5cdbf/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:3c21d92ed15e9cfc6eb64c1f5a0326db22ca9c2566ca46d845119b45b4400361", size = 4904178, upload-time = "2026-03-25T23:33:55.725Z" },
{ url = "https://files.pythonhosted.org/packages/b1/1b/bf0e01a88efd0e59679b69f42d4afd5bced8700bb5e80617b2d63a3741af/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:4668298aef7cddeaf5c6ecc244c2302a2b8e40f384255505c22875eebb47888b", size = 4441812, upload-time = "2026-03-25T23:33:57.364Z" },
{ url = "https://files.pythonhosted.org/packages/bb/8b/11df86de2ea389c65aa1806f331cae145f2ed18011f30234cc10ca253de8/cryptography-46.0.6-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:8ce35b77aaf02f3b59c90b2c8a05c73bac12cea5b4e8f3fbece1f5fddea5f0ca", size = 3963923, upload-time = "2026-03-25T23:33:59.361Z" },
{ url = "https://files.pythonhosted.org/packages/91/e0/207fb177c3a9ef6a8108f234208c3e9e76a6aa8cf20d51932916bd43bda0/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:c89eb37fae9216985d8734c1afd172ba4927f5a05cfd9bf0e4863c6d5465b013", size = 4269695, upload-time = "2026-03-25T23:34:00.909Z" },
{ url = "https://files.pythonhosted.org/packages/21/5e/19f3260ed1e95bced52ace7501fabcd266df67077eeb382b79c81729d2d3/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:ed418c37d095aeddf5336898a132fba01091f0ac5844e3e8018506f014b6d2c4", size = 4869785, upload-time = "2026-03-25T23:34:02.796Z" },
{ url = "https://files.pythonhosted.org/packages/10/38/cd7864d79aa1d92ef6f1a584281433419b955ad5a5ba8d1eb6c872165bcb/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:69cf0056d6947edc6e6760e5f17afe4bea06b56a9ac8a06de9d2bd6b532d4f3a", size = 4441404, upload-time = "2026-03-25T23:34:04.35Z" },
{ url = "https://files.pythonhosted.org/packages/09/0a/4fe7a8d25fed74419f91835cf5829ade6408fd1963c9eae9c4bce390ecbb/cryptography-46.0.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e7304c4f4e9490e11efe56af6713983460ee0780f16c63f219984dab3af9d2d", size = 4397549, upload-time = "2026-03-25T23:34:06.342Z" },
{ url = "https://files.pythonhosted.org/packages/5f/a0/7d738944eac6513cd60a8da98b65951f4a3b279b93479a7e8926d9cd730b/cryptography-46.0.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b928a3ca837c77a10e81a814a693f2295200adb3352395fad024559b7be7a736", size = 4651874, upload-time = "2026-03-25T23:34:07.916Z" },
{ url = "https://files.pythonhosted.org/packages/cb/f1/c2326781ca05208845efca38bf714f76939ae446cd492d7613808badedf1/cryptography-46.0.6-cp314-cp314t-win32.whl", hash = "sha256:97c8115b27e19e592a05c45d0dd89c57f81f841cc9880e353e0d3bf25b2139ed", size = 3001511, upload-time = "2026-03-25T23:34:09.892Z" },
{ url = "https://files.pythonhosted.org/packages/c9/57/fe4a23eb549ac9d903bd4698ffda13383808ef0876cc912bcb2838799ece/cryptography-46.0.6-cp314-cp314t-win_amd64.whl", hash = "sha256:c797e2517cb7880f8297e2c0f43bb910e91381339336f75d2c1c2cbf811b70b4", size = 3471692, upload-time = "2026-03-25T23:34:11.613Z" },
{ url = "https://files.pythonhosted.org/packages/c4/cc/f330e982852403da79008552de9906804568ae9230da8432f7496ce02b71/cryptography-46.0.6-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:12cae594e9473bca1a7aceb90536060643128bb274fcea0fc459ab90f7d1ae7a", size = 7162776, upload-time = "2026-03-25T23:34:13.308Z" },
{ url = "https://files.pythonhosted.org/packages/49/b3/dc27efd8dcc4bff583b3f01d4a3943cd8b5821777a58b3a6a5f054d61b79/cryptography-46.0.6-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:639301950939d844a9e1c4464d7e07f902fe9a7f6b215bb0d4f28584729935d8", size = 4270529, upload-time = "2026-03-25T23:34:15.019Z" },
{ url = "https://files.pythonhosted.org/packages/e6/05/e8d0e6eb4f0d83365b3cb0e00eb3c484f7348db0266652ccd84632a3d58d/cryptography-46.0.6-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ed3775295fb91f70b4027aeba878d79b3e55c0b3e97eaa4de71f8f23a9f2eb77", size = 4414827, upload-time = "2026-03-25T23:34:16.604Z" },
{ url = "https://files.pythonhosted.org/packages/2f/97/daba0f5d2dc6d855e2dcb70733c812558a7977a55dd4a6722756628c44d1/cryptography-46.0.6-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8927ccfbe967c7df312ade694f987e7e9e22b2425976ddbf28271d7e58845290", size = 4271265, upload-time = "2026-03-25T23:34:18.586Z" },
{ url = "https://files.pythonhosted.org/packages/89/06/fe1fce39a37ac452e58d04b43b0855261dac320a2ebf8f5260dd55b201a9/cryptography-46.0.6-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:b12c6b1e1651e42ab5de8b1e00dc3b6354fdfd778e7fa60541ddacc27cd21410", size = 4916800, upload-time = "2026-03-25T23:34:20.561Z" },
{ url = "https://files.pythonhosted.org/packages/ff/8a/b14f3101fe9c3592603339eb5d94046c3ce5f7fc76d6512a2d40efd9724e/cryptography-46.0.6-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:063b67749f338ca9c5a0b7fe438a52c25f9526b851e24e6c9310e7195aad3b4d", size = 4448771, upload-time = "2026-03-25T23:34:22.406Z" },
{ url = "https://files.pythonhosted.org/packages/01/b3/0796998056a66d1973fd52ee89dc1bb3b6581960a91ad4ac705f182d398f/cryptography-46.0.6-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:02fad249cb0e090b574e30b276a3da6a149e04ee2f049725b1f69e7b8351ec70", size = 3978333, upload-time = "2026-03-25T23:34:24.281Z" },
{ url = "https://files.pythonhosted.org/packages/c5/3d/db200af5a4ffd08918cd55c08399dc6c9c50b0bc72c00a3246e099d3a849/cryptography-46.0.6-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e6142674f2a9291463e5e150090b95a8519b2fb6e6aaec8917dd8d094ce750d", size = 4271069, upload-time = "2026-03-25T23:34:25.895Z" },
{ url = "https://files.pythonhosted.org/packages/d7/18/61acfd5b414309d74ee838be321c636fe71815436f53c9f0334bf19064fa/cryptography-46.0.6-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:456b3215172aeefb9284550b162801d62f5f264a081049a3e94307fe20792cfa", size = 4878358, upload-time = "2026-03-25T23:34:27.67Z" },
{ url = "https://files.pythonhosted.org/packages/8b/65/5bf43286d566f8171917cae23ac6add941654ccf085d739195a4eacf1674/cryptography-46.0.6-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:341359d6c9e68834e204ceaf25936dffeafea3829ab80e9503860dcc4f4dac58", size = 4448061, upload-time = "2026-03-25T23:34:29.375Z" },
{ url = "https://files.pythonhosted.org/packages/e0/25/7e49c0fa7205cf3597e525d156a6bce5b5c9de1fd7e8cb01120e459f205a/cryptography-46.0.6-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9a9c42a2723999a710445bc0d974e345c32adfd8d2fac6d8a251fa829ad31cfb", size = 4399103, upload-time = "2026-03-25T23:34:32.036Z" },
{ url = "https://files.pythonhosted.org/packages/44/46/466269e833f1c4718d6cd496ffe20c56c9c8d013486ff66b4f69c302a68d/cryptography-46.0.6-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6617f67b1606dfd9fe4dbfa354a9508d4a6d37afe30306fe6c101b7ce3274b72", size = 4659255, upload-time = "2026-03-25T23:34:33.679Z" },
{ url = "https://files.pythonhosted.org/packages/0a/09/ddc5f630cc32287d2c953fc5d32705e63ec73e37308e5120955316f53827/cryptography-46.0.6-cp38-abi3-win32.whl", hash = "sha256:7f6690b6c55e9c5332c0b59b9c8a3fb232ebf059094c17f9019a51e9827df91c", size = 3010660, upload-time = "2026-03-25T23:34:35.418Z" },
{ url = "https://files.pythonhosted.org/packages/1b/82/ca4893968aeb2709aacfb57a30dec6fa2ab25b10fa9f064b8882ce33f599/cryptography-46.0.6-cp38-abi3-win_amd64.whl", hash = "sha256:79e865c642cfc5c0b3eb12af83c35c5aeff4fa5c672dc28c43721c2c9fdd2f0f", size = 3471160, upload-time = "2026-03-25T23:34:37.191Z" },
]
[[package]]
name = "lab5"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "cryptography" },
]
[package.metadata]
requires-dist = [{ name = "cryptography", specifier = ">=46.0.6" }]
[[package]]
name = "pycparser"
version = "3.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" },
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
report/img/lab2-passwd.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
report/img/lab2-setup.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
report/img/lab3-acl.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
report/img/lab3-labels.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
report/img/lab3-setup.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
report/img/lab4-stand.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
report/img/lab5-enc-int.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

BIN
report/img/lab5-enc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

BIN
report/img/lab5-hmac.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

BIN
report/img/lab5-plain.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
report/img/lab5-salt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 156 KiB

View File

Before

Width:  |  Height:  |  Size: 201 KiB

After

Width:  |  Height:  |  Size: 201 KiB

View File

Before

Width:  |  Height:  |  Size: 164 KiB

After

Width:  |  Height:  |  Size: 164 KiB

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 138 KiB

View File

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

1192
report/report.tex Executable file

File diff suppressed because it is too large Load Diff