Автор статьи: Рустем Галиев

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. В реальном приложении механизм блокировки может быть более сложным, включать проверку наличия ресурса перед блокировкой, использование временных меток и т.д.


Подробнее про проектирование безопасного программного обеспечения вы можете узнать в рамках онлайн-курсов от практикующих экспертов отрасли. Подробности в каталоге.

Комментарии (3)


  1. koreychenko
    22.06.2024 12:24
    +1

    Мне показалось или тут опять смешана авторизация с аутентификацией?

    Начали с целостности передаваемых данных, а закончили надёжной идентификацией пользователя.


    1. saipr
      22.06.2024 12:24

      Смешались в кучу кони, люди


  1. garbagecollected
    22.06.2024 12:24

    deleted.