
Автор статьи: Рустем Галиев
IBM Senior DevOps Engineer & Integration Architect. Официальный DevOps ментор и коуч в IBM
Сегодня мы рассмотрим проектирование безопасного программного обеспечения для обеспечения целостности данных. Как мы можем гарантировать, что наше программное обеспечение спроектировано таким образом, чтобы данные были устойчивы к атакам и не подвергались изменениям?
Мы уже рассмотрели, как хеширование может использоваться для обеспечения конфиденциальности, но оно также часто применяется для проверки целостности. Хеширование генерирует уникальное значени�� для любых входных данных и делает это очень быстро.
Это делает его сильной формой проверки, что отправленное сообщение не было подвержено изменениям.
Предположим, у нас есть отправитель с сообщением. Перед отправкой сообщения отправитель пропускает его через хеш-функцию, чтобы получить хеш-значение. Это значение должно быть сгенерировано только на основе этого сообщения. Хеш любого другого сообщения, даже немного отличающегося, создаст другое хеш-значение.
Когда получатель получает сообщение, он также получает хеш-значение от отправителя. Получив сообщение, получатель также генерирует хеш, используя то же самое сообщение.
Если хеш совпадает с тем, который был отправлен вместе с сообщением, мы знаем, что ничего не было подвержено изменениям. Вот как мы используем хеши для проверки целостности.
import hashlib def calculate_hash(data): """Вычисление SHA-256 хеша для заданных данных""" return hashlib.sha256(data.encode()).hexdigest() # Отправитель создает сообщение и его хеш message = "This is a secure message." hash_value = calculate_hash(message) print(f"Original Message: {message}") print(f"Hash Value: {hash_value}") # Отправитель отправляет сообщение и хеш получателю # Получатель получает сообщение и хеш received_message = "This is a secure message." received_hash_value = hash_value # Получатель вычисляет хеш для полученного сообщения calculated_hash_value = calculate_hash(received_message) print(f"Received Message: {received_message}") print(f"Calculated Hash Value: {calculated_hash_value}") # Проверка целостности if calculated_hash_value == received_hash_value: print("Integrity verified: The message was not tampered with.") else: print("Integrity verification failed: The message was tampered with.")
Хеширование лишь подтверждает целостность файла. Оно не говорит о том, кто отправил файл в первую очередь. Для этого нам нужен способ проверки личности отправителя (отказ от возражений), который мы можем осуществить с помощью асимметричного шифрования.
Разработчик хеширует исходный код, чтобы получить хеш-значение, которое будет подтверждать целостность файла. Затем они упаковывают как код, так и хеш-значение вместе и подписывают это своим личным ключом.
Это шифрует пакет таким образом, что только публичный ключ разработчика - общий ключ - может расшифровать его.
Затем пакет отправляется как подписанный пакет, который подтверждает целостность файла и личность разработчика. Давайте посмотрим, как это работает.

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

Успешное расшифрование с использованием публичного ключа доказывает, что именно разработчик отправил пакет, так как только у разработчика есть личный ключ для его шифрования или подписи.
Затем получатель хеширует код, чтобы получить хеш-значение. Если оно совпадает с хеш-значением, отправленным разработчиком, получатель знает, что файл не был изменен, и это был именно разработчик, кто его отправил.
Подпись кода часто используется в контексте отправителя и получателя. Во многих современных системах она также используется для обеспечения того, что данные не были изменены перед выполнением.
В качестве примера, операционная система iPhone запускается только в случае успешного прохождения ряда тестов на основе подписи кода, чтобы убедиться, что ОС не была изменена.
Еще одной важной концепцией является ссылочная целостность. Мы видим ссылочную целостность чаще всего в случае файлов или данных с отношениями.
Например, если вы удаляете папку на своем рабочем столе, логично, что файлы внутри папки также должны быть удалены.
В программном обеспечении мы часто сталкиваемся с этим в базах данных, где наши данные имеют определенные отношения. Мы хотим сохранить целостность базы данных без оставшихся записей или несоответствующих отношений.
В данном случае у нас есть три таблицы в базе данных. Первая таблица предназначена для пользователей, следующая для платежей, и последняя для адресов. Отношение очевидно для человека: информация о платеже связана с пользователем.
Если мы удаляем пользователя, мы не хотим, чтобы оставались записи в базе данных, поэтому необходимо также убедиться, что метод платежа и адрес также будут удалены.
Это пример ссылочной целостности, который помогает поддерживать высокий уровень целостности данных в базе данных.

Допустим, команды бизнеса получают доступ к файлу в системе. Когда они получают доступ к ресурсу, ресурс затем блокируется. Если аудиторская команда хочет получить доступ к тому же ресурсу в то же время, они не могут его изменить, потому что он находится в заблокированном состоянии.
Это обеспечи��ает, что аудиторская команда может вносить изменения только после того, как команда бизнеса закончит свои изменения. Это гарантирует целостность данных.

Однако необходимо быть осторожным с блокировкой ресурсов, потому что при ее плохой реализации она может стать уязвимым местом для хакеров. Представьте ситуацию, когда хакер просто блокирует каждый ресурс от других пользователей и вызывает отказ в обслуживании.
Это важно учитывать при проектировании механизма блокировки ресурсов в вашем программном обеспечении.
import threading import time # Пример ресурса, который может быть заблокирован class Resource: def __init__(self): self.lock = threading.Lock() # Мьютекс для блокировки ресурса self.is_locked = False def access_resource(self, team_name): with self.lock: while self.is_locked: print(f"Resource is locked, {team_name} is waiting.") time.sleep(1) self.is_locked = True print(f"{team_name} has accessed the resource.") time.sleep(5) # Предполагаемая работа с ресурсом self.is_locked = False print(f"{team_name} has finished and released the resource.") # Создаем экземпляр ресурса resource = Resource() # Пример команд, которые пытаются получить доступ к ресурсу def business_team(): resource.access_resource("Business Team") def audit_team(): resource.access_resource("Audit Team") # Создаем потоки для команд thread_business = threading.Thread(target=business_team) thread_audit = threading.Thread(target=audit_team) # Запускаем потоки thread_business.start() thread_audit.start() # Ждем завершения потоков thread_business.join() thread_audit.join() print("All teams have finished accessing the resource.")
Если разобраться, то у нас есть
Класс Resource:
init: Инициализирует мьютекс (self.lock) и флаг self.is_locked, указывающий на заблокированное состояние ресурса.
Метод access_resource(team_name):
Использует контекстный менеджер with self.lock: для захвата мьютекса.
Если self.is_locked == True, поток ожидает освобождения ресурса (while self.is_locked: ...).
После захвата ресурса выводится сообщение о доступе команды ({team_name} has accessed the resource.).
Предполагается работа с ресурсом (представлено time.sleep(5)).
После завершения работы ресурс освобождается (self.is_locked = False) и выводится сообщение об освобождении ресурса ({team_name} has finished and released the resource.).
Функции business_team() и audit_team():
Примеры команд, которые пытаются получить доступ к ресурсу. Они вызывают метод resource.access_resource() с указанием имени команды.
Создание и запуск потоков:
Создаются два потока (thread_business и thread_audit), каждый из которых вызывает функцию соответствующей команды.
Потоки запускаются методом start().
Ожидание завершения потоков:
Главный поток (основная программа) ждет завершения потоков с помощью метода join().
Вывод:
После завершения работы всех команд выводится сообщение "All teams have finished accessing the resource.".
Этот пример демонстрирует базовую реализацию механизма блокировки ресурсов с использованием мьютекса в Python. В реальном приложении механизм блокировки может быть более сложным, включать проверку наличия ресурса перед блокировкой, использование временных меток и т.д.
Подробнее про проектирование безопасного программного обеспечения вы можете узнать в рамках онлайн-курсов от практикующих экспертов отрасли. Подробности в каталоге.
