Автор статьи: Рустем Галиев
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. В реальном приложении механизм блокировки может быть более сложным, включать проверку наличия ресурса перед блокировкой, использование временных меток и т.д.
Подробнее про проектирование безопасного программного обеспечения вы можете узнать в рамках онлайн-курсов от практикующих экспертов отрасли. Подробности в каталоге.
koreychenko
Мне показалось или тут опять смешана авторизация с аутентификацией?
Начали с целостности передаваемых данных, а закончили надёжной идентификацией пользователя.
saipr