Изучение HTTPS с помощью Python

Оглавление

Задумывались ли вы когда-нибудь, почему для вас нормально отправлять данные своей кредитной карты через Интернет? Возможно, вы заметили пометку https:// на URL-адресах в вашем браузере, но что это такое и как она обеспечивает безопасность вашей информации? Или, возможно, вы хотите создать HTTPS-приложение на Python, но не совсем уверены, что это означает. Как вы можете быть уверены, что ваше веб-приложение безопасно?

Возможно, вы удивитесь, узнав, что не обязательно быть экспертом в области безопасности, чтобы ответить на эти вопросы! В этом руководстве вы получите практические знания о различных факторах, которые в совокупности обеспечивают безопасность коммуникаций через Интернет. Вы увидите конкретные примеры того, как HTTPS-приложение на Python обеспечивает безопасность информации.

В этом руководстве вы узнаете, как:

  • Мониторинг и анализ сетевого трафика
  • Применяйте криптографию для обеспечения безопасности данных
  • Опишите основные концепции Инфраструктуры открытых ключей (PKI)
  • Создайте свой собственный центр сертификации
  • Создание HTTPS-приложения на Python
  • Определение распространенных предупреждений и ошибок Python HTTPS

Что такое HTTP?

Прежде чем вы углубитесь в HTTPS и его использование в Python, важно понять его родительский элемент, HTTP. Эта аббревиатура расшифровывается как Протокол передачи гипертекста, который лежит в основе большинства коммуникаций, происходящих при просмотре ваших любимых веб-сайтов. Более конкретно, HTTP - это способ, с помощью которого пользовательский агент, такой как ваш веб-браузер, взаимодействует с веб-сервером, например, realpython.com . Вот упрощенная схема HTTP-взаимодействия:

HTTP handshake flow

На этой диаграмме показана упрощенная версия взаимодействия вашего компьютера с сервером. Вот краткое описание каждого шага:

  1. Вы указываете своему браузеру перейти к http://someserver.com/link.
  2. Ваше устройство и сервер устанавливают TCP-соединение.
  3. Ваш браузер отправляет HTTP-запрос на сервер.
  4. Сервер получает HTTP-запрос и анализирует его.
  5. Сервер отправляет HTTP-ответ .
  6. Ваш компьютер получает, анализирует и отображает ответ.

В этом разделе описываются основы HTTP. Вы отправляете запрос на сервер, и сервер возвращает ответ. Хотя для HTTP не требуется TCP, он требует надежного протокола более низкого уровня. На практике это почти всегда протокол TCP over IP (хотя Google пытается создать замену ). Если вам нужно освежить знания, ознакомьтесь с Программированием сокетов на Python (руководство).

Среди существующих протоколов HTTP является одним из самых простых. Он был разработан для отправки контента через Интернет, такого как HTML, видео, изображения и т.д. Это делается с помощью HTTP-запроса и ответа. HTTP-запросы содержат следующие элементы:

  • Метод описывает, какое действие хочет выполнить клиент. Для статического содержимого обычно используется метод GET, хотя есть и другие доступные методы, такие как POST, HEAD, и DELETE.
  • Путь указывает серверу, какую веб-страницу вы хотели бы запросить. Например, путь к этой странице следующий /python-https.
  • Версия является одной из нескольких версий HTTP, таких как 1.0, 1.1 или 2.0. Наиболее распространенным, вероятно, является 1.1.
  • Заголовки помогают описать дополнительную информацию для сервера.
  • Тело предоставляет серверу информацию от клиента. Хотя это поле не является обязательным, для некоторых методов характерно наличие тела, например POST.

Это инструменты, которые ваш браузер использует для связи с сервером. Сервер отправляет HTTP-ответ. HTTP-ответ содержит следующие элементы:

  • Версия идентифицирует версию HTTP, которая обычно совпадает с версией запроса.
  • Код состояния указывает, был ли запрос успешно выполнен. Существует довольно много кодов состояния .
  • Сообщение о состоянии содержит понятное пользователю сообщение, которое помогает описать код состояния.
  • Заголовки позволяют серверу отвечать дополнительными метаданными о запросе. Это та же концепция, что и заголовки запросов.
  • Текст содержит содержимое. Технически это необязательно, но обычно содержит полезный ресурс.

Это строительные блоки для HTTP. Если вам интересно узнать больше о протоколе HTTP, вы можете ознакомиться с обзорной страницей , чтобы более подробно ознакомиться с протоколом.

Что такое HTTPS?

Теперь, когда вы немного больше понимаете о протоколе HTTP, что такое HTTPS? Хорошая новость в том, что вы уже знаете об этом! HTTPS расшифровывается как Безопасный протокол передачи гипертекста. По сути, HTTPS - это тот же протокол, что и HTTP, но с дополнительным значением безопасности связи.

HTTPS не переписывает ни один из принципов HTTP, на которых он построен. Вместо этого HTTPS состоит из обычного HTTP, передаваемого по зашифрованному соединению. Как правило, это зашифрованное соединение обеспечивается либо TLS, либо SSL, которые являются криптографическими протоколами, которые шифруют информацию перед ее отправкой по сети.

Примечание: TLS и SSL - очень похожие протоколы, хотя SSL уже выходит из употребления, и его место займет TLS. Различия в этих протоколах выходят за рамки данного руководства. Достаточно знать, что TLS - это более новая, улучшенная версия SSL.

Итак, зачем создавать это разделение? Почему бы просто не внести сложность в сам протокол HTTP? Ответ заключается в переносимости. Защита связи - важная и сложная задача, но HTTP - это лишь один из многих протоколов, требующих обеспечения безопасности. Существует бесчисленное множество других протоколов для самых разных приложений:

Есть и другие! Если бы каждому из этих протоколов пришлось создавать свой собственный механизм безопасности, мир был бы гораздо менее защищенным и гораздо более запутанным. TLS, который часто используется в вышеупомянутых протоколах, предоставляет общий метод защиты коммуникаций.

Примечание: Такое разделение протоколов является настолько распространенной темой в сетевом взаимодействии, что у него есть название. Модель OSI представляет собой обмен данными с физического носителя вплоть до HTML-кода, отображаемого на этой странице!

Почти вся информация, которую вы узнаете из этого руководства, будет применима не только к HTTPS-приложениям на Python. Вы познакомитесь с основами безопасной связи, а также с тем, как она применяется конкретно к HTTPS.

Почему важен протокол HTTPS?

Безопасные коммуникации имеют решающее значение для обеспечения безопасной онлайн-среды. По мере того, как все больше людей в мире подключаются к Интернету, включая банки и медицинские учреждения, разработчикам становится все более важно создавать HTTPS-приложения на Python. Опять же, HTTPS - это просто HTTP через TLS или SSL. Протокол TLS предназначен для обеспечения конфиденциальности от подслушивающих устройств. Он также может обеспечивать аутентификацию как клиента, так и сервера.

В этом разделе вы подробно изучите эти концепции, выполнив следующие действия:

  1. Создание HTTPS-сервера на Python
  2. Взаимодействие с вашим HTTPS-сервером Python
  3. Фиксация этих сообщений
  4. Анализ этих сообщений

Давайте начнем!

Создание примера приложения

Предположим, вы лидер крутого клуба питонов под названием "Секретные белки". Поскольку белки являются секретными, для посещения их собраний требуется секретное сообщение. Как лидер, вы выбираете секретное сообщение, которое меняется для каждой встречи. Однако иногда вам бывает трудно встретиться со всеми участниками перед собранием, чтобы передать им секретное сообщение! Вы решаете создать секретный сервер, на котором участники могут просто сами просматривать секретное сообщение.

Примечание: Пример кода, используемый в этом руководстве, не предназначен для использования в рабочей среде. Он разработан, чтобы помочь вам освоить основы HTTP и TLS. Пожалуйста, не используйте этот код в производственных целях. Во многих приведенных ниже примерах используются ужасные методы обеспечения безопасности. В этом руководстве вы узнаете о TLS и об одном из способов, которым он может помочь вам обеспечить большую безопасность.

Вы ознакомились с некоторыми руководствами по реальному Python и решили использовать некоторые известные вам зависимости:

  • Flask для создания веб-приложения
  • uWSGI в качестве рабочего сервера
  • запросы на использование вашего сервера

Для установки всех этих зависимостей вы можете использовать pip:

$ pip install flask uwsgi requests

Установив зависимости, вы начинаете писать свое приложение. В файле с именем server.py вы создаете Flask приложение:

# server.py
from flask import Flask

SECRET_MESSAGE = "fluffy tail"
app = Flask(__name__)

@app.route("/")
def get_secret_message():
    return SECRET_MESSAGE

Это приложение Flask будет отображать секретное сообщение всякий раз, когда кто-либо посетит / путь к вашему серверу. После этого вы развертываете свое приложение на своем секретном сервере и запускаете его:

$ uwsgi --http-socket 127.0.0.1:5683 --mount /=server:app

Эта команда запускает сервер, используя приложение Flask, описанное выше. Вы запускаете его на странном порту, потому что не хотите, чтобы люди могли его найти, и хвалите себя за хитрость! Вы можете убедиться, что это работает, посетив http://localhost:5683 в вашем браузере.

Поскольку все в Secret Squirrels знают Python, вы решаете помочь им. Вы пишете скрипт под названием client.py, который поможет им получить секретное сообщение:

# client.py
import os
import requests

def get_secret_message():
    url = os.environ["SECRET_URL"]
    response = requests.get(url)
    print(f"The secret message is: {response.text}")

if __name__ == "__main__":
    get_secret_message()

Этот код будет выводить секретное сообщение до тех пор, пока у них установлена переменная окружения SECRET_URL. В этом случае SECRET_URL равно 127.0.0.1:5683. Итак, ваш план состоит в том, чтобы дать каждому члену клуба секретный URL-адрес и попросить их хранить его в тайне и в безопасности.

Хотя это может показаться нормальным, будьте уверены, это не так! На самом деле, даже если вы введете имя пользователя и пароль на этом сайте, это все равно будет небезопасно. Но даже если вашей команде каким-то образом удастся сохранить URL-адрес в безопасности, ваше секретное сообщение все равно не будет защищено. Чтобы продемонстрировать, почему, вам нужно немного узнать о мониторинге сетевого трафика. Для этого вы будете использовать инструмент под названием Wireshark.

Настройка Wireshark

Wireshark - широко используемый инструмент для анализа сетей и протоколов. Это означает, что он может помочь вам увидеть, что происходит при подключении к сети. Установка и настройка Wireshark в этом руководстве не обязательны, но вы можете продолжить, если захотите. На странице загрузки доступно несколько программ установки:

  • macOS 10.12 и более поздних версий
  • 64-разрядный установщик Windows
  • 32-разрядный установщик Windows

Если вы используете Windows или Mac, то у вас должна быть возможность загрузить соответствующий установщик и следовать инструкциям. В итоге у вас должен быть запущен Wireshark.

Если вы используете среду Linux на базе Debian, то установка будет немного сложнее, но все же возможна. Вы можете установить Wireshark с помощью следующих команд:

$ sudo add-apt-repository ppa:wireshark-dev/stable
$ sudo apt-get update
$ sudo apt-get install wireshark
$ sudo wireshark

Перед вами должен появиться экран, который выглядит примерно так:

Wireshark's main screen

Теперь, когда Wireshark запущен, пришло время проанализировать трафик!

Видя, Что Ваши Данные Небезопасны

То, как работают ваши текущие клиент и сервер, небезопасно. HTTP отправит все данные в открытом виде, чтобы их мог увидеть любой желающий. Это означает, что даже если у кого-то нет вашего SECRET_URL, он все равно может видеть все, что вы делаете, при условии, что он может отслеживать трафик на любом устройстве между вами и сервером.

Это должно вас немного пугать. В конце концов, вы же не хотите, чтобы другие люди приходили на ваши тайные встречи с белочками! Вы можете доказать, что это происходит на самом деле. Сначала запустите свой сервер, если он у вас еще не запущен:

$ uwsgi --http-socket 127.0.0.1:5683 --mount /=server:app

Это запустит ваше приложение Flask на порту 5683. Далее вы запустите сбор пакетов в Wireshark. Этот сбор пакетов поможет вам увидеть весь трафик, поступающий на сервер и с сервера-получателя. Начните с выбора интерфейса Loopback:lo в Wireshark:

Wireshark with Loopback selected

Вы можете видеть, что выделена часть Loopback:lo. Это дает Wireshark указание отслеживать трафик на этом порту. Вы можете поступить лучше и указать, какой порт и протокол вы хотите перехватить. Вы можете ввести port 5683 в фильтре захвата и http в фильтре отображения:

Wireshark with port 5683 filled out

Зеленый флажок означает, что Wireshark удовлетворен введенным вами фильтром. Теперь вы можете начать съемку, нажав на плавник в левом верхнем углу:

Wireshark with loopback and port filtering clicked

При нажатии на эту кнопку в Wireshark откроется новое окно:

Wireshark's capture page with nothing captured

Это новое окно довольно простое, но сообщение внизу гласит <live capture in progress>, что указывает на то, что оно работает. Не беспокойтесь, что ничего не отображается, это нормально. Чтобы Wireshark сообщал о чем-либо, на вашем сервере должна быть какая-то активность. Чтобы получить какие-то данные, попробуйте запустить свой клиент:

$ SECRET_URL="http://127.0.0.1:5683" python client.py
The secret message is: fluffy tail

После выполнения кода client.py, приведенного выше, вы должны увидеть несколько записей в Wireshark. Если все прошло хорошо, то вы увидите две записи, которые выглядят примерно так:

Wireshark with HTTP request and response captured

Эти две записи представляют две части произошедшего обмена данными. Первая часть - это запрос клиента к вашему серверу. Когда вы нажмете на первую запись, вы увидите множество информации:

The first HTTP request in Wireshark

Это очень много информации! Вверху у вас все еще есть ваш HTTP-запрос и ответ на него. Как только вы выберете одну из этих записей, вы увидите, что средняя и нижняя строки заполнятся информацией.

В средней строке приведена разбивка протоколов, которые Wireshark смог идентифицировать для выбранного запроса. Эта разбивка позволяет вам изучить, что на самом деле произошло в вашем HTTP-запросе. Вот краткое изложение информации, которую Wireshark описывает в средней строке сверху вниз:

  1. Физический уровень: В этой строке описывается физический интерфейс, используемый для отправки запроса. В вашем случае это, вероятно, идентификатор интерфейса 0 (lo) для вашего интерфейса обратной связи.
  2. Информация об Ethernet: В этой строке показан протокол уровня 2, который включает MAC-адреса источника и назначения.
  3. IPv4: В этой строке отображаются IP-адреса источника и назначения (127.0.0.1).
  4. TCP: Эта строка содержит требуемое подтверждение связи по протоколу TCP для создания надежного канала передачи данных.
  5. HTTP: В этой строке отображается информация о самом HTTP-запросе.

Когда вы раскрываете уровень протокола передачи гипертекста, вы можете увидеть всю информацию, которая составляет HTTP-запрос:

HTTP Request with expanded details in wireshark

На этом изображении показан HTTP-запрос вашего скрипта:

  • Способ: GET
  • Путь: /
  • Версия: 1.1
  • Заголовки: Host: 127.0.0.1:5683, Connection: keep-alive, и другие
  • Тело: Тела нет

Последняя строка, которую вы увидите, - это шестнадцатеричный дамп данных. Вы можете заметить, что в этом шестнадцатеричном дампе вы действительно можете видеть части вашего HTTP-запроса. Это потому, что ваш HTTP-запрос был отправлен в открытом виде. Но как насчет ответа? Если вы нажмете на HTTP-ответ, то увидите аналогичное представление:

wireshark with HTTP response expanded

И снова у вас те же три раздела. Если вы внимательно посмотрите на шестнадцатеричный файл, то увидите секретное сообщение в виде обычного текста! Это большая проблема для секретных белок. Это означает, что любой человек, обладающий некоторыми техническими знаниями, может очень легко просмотреть этот трафик, если ему это интересно. Итак, как вы решаете эту проблему? Ответ - криптография.

Как помогает криптография?

В этом разделе вы узнаете об одном из способов обеспечения безопасности ваших данных, создав свои собственные криптографические ключи и используя их как на вашем сервере, так и на вашем клиенте. Хотя это и не будет вашим последним шагом, он поможет вам получить прочную основу для создания HTTPS-приложений на Python.

Понимание основ криптографии

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

Поначалу криптография может показаться пугающей, но основные понятия довольно доступны. На самом деле, вы, вероятно, уже практиковали криптографию раньше. Если у вас когда-либо был секретный язык общения с друзьями, и вы использовали его для обмена заметками в классе, значит, вы практиковались в криптографии. (Если вы этого не делали, то не волнуйтесь — вы скоро это сделаете.)

Каким-то образом вам нужно взять строку "fluffy tail" и преобразовать ее во что-то непонятное. Один из способов сделать это - сопоставить определенные символы с другими символами. Эффективный способ сделать это - сдвинуть символы в алфавите на одну позицию назад. Это выглядело бы примерно так:

An alphabet cipher shifted by 1 space

На этом рисунке показано, как перевести текст с исходного алфавита на новый и обратно. Итак, если бы у вас было сообщение ABC, то на самом деле вы бы отправили сообщение ZAB. Если вы примените это к "fluffy tail", то, предполагая, что пробелы останутся прежними, вы получите ekteex szhk. Хотя это и не идеально, но, вероятно, покажется бессмыслицей любому, кто это увидит.

Поздравляем! Вы создали то, что в криптографии известно как шифр, который описывает, как преобразовать открытый текст в зашифрованный текст и обратно. В данном случае ваш шифр описан на английском языке. Этот конкретный тип шифра называется шифром подстановки. По сути, это тот же тип шифра, который использовался в машине Enigma, хотя и в гораздо более простой версии.

Теперь, если бы вы хотели передать сообщение Секретным белочкам, вам сначала нужно было бы сказать им, на сколько букв нужно сдвинуть, а затем передать им зашифрованное сообщение. В Python это может выглядеть примерно так:

CIPHER = {"a": "z", "A": "Z", "b": "a"} # And so on

def encrypt(plaintext: str):
    return "".join(CIPHER.get(letter, letter) for letter in plaintext)

Здесь вы создали функцию под названием encrypt(), которая принимает открытый текст и преобразует его в зашифрованный текст. Представьте, что у вас есть словарь CIPHER на нем нанесены все персонажи. Аналогично, вы могли бы создать decrypt():

DECIPHER = {v: k for k, v in CIPHER.items()}

def decrypt(ciphertext: str):
    return "".join(DECIPHER.get(letter, letter) for letter in ciphertext)

Эта функция противоположна encrypt(). Она принимает зашифрованный текст и преобразует его в открытый текст. В этом варианте шифрования у вас есть специальный ключ, который пользователи должны знать для шифрования и расшифровки сообщений. В приведенном выше примере этот ключ равен 1. То есть в шифре указано, что вы должны сдвинуть каждую букву на один символ назад. Ключ очень важно сохранить в тайне, потому что любой, у кого есть ключ, может легко расшифровать ваше сообщение.

Примечание: Хотя вы могли бы использовать это для своего шифрования, это все равно не очень безопасно. Этот шифр легко взломать с помощью частотного анализа и он слишком примитивен для секретных белок.

В современную эпоху криптография стала намного более совершенной. Для обеспечения безопасности она опирается на сложную математическую теорию и компьютерные науки. Хотя математика, лежащая в основе этих шифров, выходит за рамки данного руководства, основные концепции остаются прежними. У вас есть шифр, который описывает, как взять открытый текст и преобразовать его в зашифрованный текст.

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

Использование криптографии в HTTPS-приложениях на Python

К счастью для вас, вам не обязательно быть экспертом в математике или компьютерных науках, чтобы использовать криптографию. В Python также есть secrets модуль, который может помочь вам генерировать криптографически защищенные случайные данные. В этом руководстве вы узнаете о библиотеке Python с подходящим названием cryptography. Он доступен на PyPI, так что вы можете установить его с помощью pip:

$ pip install cryptography

Это приведет к установке cryptography в вашу виртуальную среду. Установив cryptography, вы теперь можете шифровать и расшифровывать данные математически безопасным способом, используя метод Fernet.

Напомним, что ваш секретный ключ в вашем шифре был 1. Аналогично, вам нужно создать ключ для корректной работы Fernet:

>>> from cryptography.fernet import Fernet
>>> key = Fernet.generate_key()
>>> key
b'8jtTR9QcD-k3RO9Pcd5ePgmTu_itJQt9WKQPzqjrcoM='

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

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

Этот ключ работает так же, как и предыдущий. Он необходим для перехода к зашифрованному тексту и обратно к открытому. Теперь самое интересное! Вы можете зашифровать сообщение следующим образом:

>>> my_cipher = Fernet(key)
>>> ciphertext = my_cipher.encrypt(b"fluffy tail")
>>> ciphertext
b'gAAAAABdlW033LxsrnmA2P0WzaS-wk1UKXA1IdyDpmHcV6yrE7H_ApmSK8KpCW-6jaODFaeTeDRKJMMsa_526koApx1suJ4_dQ=='

В этом коде вы создали объект Fernet с именем my_cipher, который затем можно использовать для шифрования вашего сообщения. Обратите внимание, что ваше секретное сообщение "fluffy tail" должно быть объектом bytes, чтобы его можно было зашифровать. После шифрования вы можете увидеть, что ciphertext представляет собой длинный поток байт.

Благодаря Fernet, этим зашифрованным текстом невозможно манипулировать или читать без ключа! Для этого типа шифрования требуется, чтобы и сервер, и клиент имели доступ к ключу. Когда обеим сторонам требуется один и тот же ключ, это называется симметричным шифрованием. В следующем разделе вы увидите, как использовать это симметричное шифрование для обеспечения безопасности ваших данных.

Следить За Сохранностью Ваших Данных

Теперь, когда вы понимаете некоторые основы криптографии на Python, вы можете применить эти знания на своем сервере. Создайте новый файл с именем symmetric_server.py:

# symmetric_server.py
import os
from flask import Flask
from cryptography.fernet import Fernet

SECRET_KEY = os.environb[b"SECRET_KEY"]
SECRET_MESSAGE = b"fluffy tail"
app = Flask(__name__)

my_cipher = Fernet(SECRET_KEY)

@app.route("/")
def get_secret_message():
    return my_cipher.encrypt(SECRET_MESSAGE)

Этот код объединяет исходный серверный код с объектом Fernet, который вы использовали в предыдущем разделе. Ключ теперь считывается как bytes объект из среды с помощью os.environb. Теперь, когда сервер удален, вы можете сосредоточиться на клиенте. Вставьте следующее в symmetric_client.py:

# symmetric_client.py
import os
import requests
from cryptography.fernet import Fernet

SECRET_KEY = os.environb[b"SECRET_KEY"]
my_cipher = Fernet(SECRET_KEY)

def get_secret_message():
    response = requests.get("http://127.0.0.1:5683")

    decrypted_message = my_cipher.decrypt(response.content)
    print(f"The codeword is: {decrypted_message}")

if __name__ == "__main__":
    get_secret_message()

Еще раз повторю, что это измененный код для объединения вашего предыдущего клиента с механизмом шифрования Fernet. get_secret_message() выполняет следующее:

  1. Сделайте запрос к вашему серверу.
  2. Возьмите необработанных байт из ответа.
  3. Попытайтесь расшифровать необработанные байты.
  4. Распечатайте расшифрованное сообщение.

Если вы запустите и сервер, и клиент, то увидите, что вы успешно зашифровали и расшифровали свое секретное сообщение:

$ uwsgi --http-socket 127.0.0.1:5683 \
    --env SECRET_KEY="8jtTR9QcD-k3RO9Pcd5ePgmTu_itJQt9WKQPzqjrcoM=" \
    --mount /=symmetric_server:app

В этом вызове вы снова запускаете сервер через порт 5683. На этот раз вы передаете SECRET_KEY, который должен быть как минимум строкой в кодировке base64 длиной 32 дюйма. После перезапуска вашего сервера теперь вы можете запросить его:

$ SECRET_KEY="8jtTR9QcD-k3RO9Pcd5ePgmTu_itJQt9WKQPzqjrcoM=" python symmetric_client.py
The secret message is: b'fluffy tail'

Ух ты! Вам удалось зашифровать и расшифровать свое сообщение. Если вы попытаетесь запустить его с недопустимым SECRET_KEY, то получите сообщение об ошибке:

$ SECRET_KEY="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" python symmetric_client.py
Traceback (most recent call last):
  File ".../cryptography/fernet.py", line 104, in _verify_signature
    h.verify(data[-32:])
  File ".../cryptography/hazmat/primitives/hmac.py", line 66, in verify
    ctx.verify(signature)
  File ".../cryptography/hazmat/backends/openssl/hmac.py", line 74, in verify
    raise InvalidSignature("Signature did not match digest.")
cryptography.exceptions.InvalidSignature: Signature did not match digest.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "symmetric_client.py", line 16, in <module>
    get_secret_message()
  File "symmetric_client.py", line 11, in get_secret_message
    decrypted_message = my_cipher.decrypt(response.content)
  File ".../cryptography/fernet.py", line 75, in decrypt
    return self._decrypt_data(data, timestamp, ttl)
  File ".../cryptography/fernet.py", line 117, in _decrypt_data
    self._verify_signature(data)
  File ".../cryptography/fernet.py", line 106, in _verify_signature
    raise InvalidToken
cryptography.fernet.InvalidToken

Итак, вы знаете, что шифрование и дешифрование работают. Но насколько это безопасно? Да, это так. Чтобы убедиться в этом, вы можете вернуться в Wireshark и запустить новый захват с теми же фильтрами, что и раньше. После настройки захвата снова запустите клиентский код:

$ SECRET_KEY="8jtTR9QcD-k3RO9Pcd5ePgmTu_itJQt9WKQPzqjrcoM=" python symmetric_client.py
The secret message is: b'fluffy tail'

Вы выполнили еще один успешный HTTP-запрос и получили ответ, и снова видите эти сообщения в Wireshark. Поскольку секретное сообщение передается только в ответе, вы можете нажать на него, чтобы просмотреть данные:

Wireshark's view of the HTTP response that was encrypted using symmetric encryption

В средней строке этого рисунка вы можете увидеть данные, которые были фактически переданы:

gAAAAABdlXSesekh9LYGDpZE4jkxm4Ai6rZQg2iHaxyDXkPWz1O74AB37V_a4vabF13fEr4kwmCe98Wlr8Zo1XNm-WjAVtSgFQ==

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

Ваши данные в безопасности! Но подождите минутку — вам никогда не нужно было ничего знать о ключе, когда вы раньше использовали HTTPS-приложения на Python. Это потому, что HTTPS не использует исключительно симметричное шифрование. Как оказалось, делиться секретами - непростая задача.

Чтобы подтвердить эту концепцию, перейдите к http://127.0.0.1:5683 в вашем браузере, и вы увидите зашифрованный текст ответа. Это потому, что ваш браузер ничего не знает о вашем секретном ключе шифрования. Так как же на самом деле работают HTTPS-приложения на Python? Вот тут-то и вступает в игру асимметричное шифрование.

Как осуществляется обмен Ключами?

В предыдущем разделе вы видели, как можно использовать симметричное шифрование для обеспечения безопасности ваших данных при их передаче по Интернету. Тем не менее, несмотря на то, что симметричное шифрование является безопасным, это не единственный метод шифрования, используемый приложениями HTTPS на Python для обеспечения безопасности ваших данных. Симметричное шифрование создает некоторые фундаментальные проблемы, которые не так-то просто решить.

Примечание: Помните, что для симметричного шифрования требуется наличие общего ключа между клиентом и сервером. К сожалению, безопасность зависит только от вашего самого слабого звена, а слабые звенья особенно опасны при симметричном шифровании. Как только один человек скомпрометирует ключ, все ключи будут скомпрометированы. Можно с уверенностью предположить, что любая система безопасности в какой-то момент будет скомпрометирована.

Итак, как вы можете изменить свой ключ? Если у вас только один сервер и один клиент, то это может оказаться несложной задачей. Однако по мере того, как у вас появляется все больше клиентов и серверов, требуется все больше координации, чтобы изменить ключ и эффективно сохранить ваши секреты в безопасности.

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

Если вы справитесь со сменой ключа, то вам предстоит решить еще одну проблему. Как вы поделитесь своим первоначальным ключом? В примере с Secret Squirrels вы решили эту проблему, получив физический доступ к каждому из участников. Вы могли бы лично поделиться секретом с каждым участником и попросить его хранить это в тайне, но помните, что кто-то будет самым слабым звеном.

Теперь предположим, что вы добавили участника в Secret Squirrels из другого физического местоположения. Как вы поделитесь секретом с этим участником? Заставляете ли вы их летать к вам каждый раз, когда меняется ключ? Было бы неплохо, если бы вы могли разместить секретный ключ на своем сервере и автоматически предоставлять к нему общий доступ. К сожалению, это лишило бы саму цель шифрования, поскольку секретный ключ мог получить любой!

Конечно, вы могли бы дать каждому первоначальный мастер-ключ, чтобы получить секретное сообщение, но теперь у вас в два раза больше проблем, чем раньше. Если у вас болит голова, не волнуйтесь! Вы не единственный.

Все, что вам нужно, - это чтобы у двух сторон, которые никогда не общались, был общий секрет. Звучит невероятно, не так ли? К счастью, трое парней по имени Ральф Меркл, Уитфилд Диффи и Мартин Хеллман имеют твоя спина. Они помогли продемонстрировать, что криптография с открытым ключом, иначе известная как асимметричное шифрование, возможна.

Примечание: Хотя Уитфилд Диффи и Мартин Хеллман широко известны как первые, кто обнаружил эту схему, в 1997 году стало известно, что трое мужчин, работающих на GCHQ по имени Джеймс Х. Эллис, Клиффорд Кокс и Малкольм Дж. Уильямсон имели ранее мы демонстрировали эту способность семью годами ранее!

Асимметричное шифрование позволяет двум пользователям, которые никогда ранее не общались, поделиться общим секретом. Один из самых простых способов понять основные принципы - использовать цветовую аналогию. Представьте, что у вас есть следующий сценарий:

Initial setup of Diffie Hellman Key Exchange

На этой диаграмме вы пытаетесь связаться с секретной системой, с которой никогда раньше не встречались, но шпион может видеть все, что вы отправляете. Вы знаете о симметричном шифровании и хотели бы его использовать, но сначала вам нужно поделиться секретом. К счастью, у вас обоих есть секретный ключ. К сожалению, вы не можете отправить свой секретный ключ, потому что его увидит шпион. Так что же вы делаете?

Первое, что вам нужно сделать, это договориться с вашим партнером о цвете, например, желтом:

Shared Colors of Diffie Hellman Key Exchange

Обратите внимание, что шпион может видеть общий цвет, как и вы, и Секретная белка. Общий цвет фактически является общедоступным. Теперь и вы, и Секретная белка объедините свои личные ключи с общим цветом:

Combined colors Diffie Hellman Key Exchange

Сочетание ваших цветов дает зеленый, а сочетание цветов Секретной белки - оранжевый. Вы оба закончили с общим цветом, и теперь вам нужно поделиться своими комбинированными цветами друг с другом:

Exchanging combined colors in Diffie Hellman Key Exchange

Теперь у вас есть ваш личный ключ и комбинированный цвет Секретной белки. Аналогично, у Секретной Белки есть свой личный ключ и ваш комбинированный цвет. Вы с Секретной Белкой довольно быстро объединили свои цвета.

В spy, однако, есть только эти комбинированные цвета. Пытаться определить ваш точный исходный цвет очень сложно, даже учитывая исходный общий цвет. Шпиону пришлось бы пойти в магазин и купить много разных оттенков синего, чтобы попробовать. Даже в этом случае было бы трудно понять, правильный ли оттенок зеленого они выбрали после сочетания! Короче говоря, ваш закрытый ключ по-прежнему остается закрытым.

Но что насчет вас и Секретной Белки? У вас по-прежнему нет общего секрета! Именно здесь вы получите свой секретный ключ. Если вы объедините свой закрытый ключ с комбинированным цветом, который вы получили от Секретной белки, то у вас получится один и тот же цвет:

Shared secret using the Diffie Hellman Key Exchange

Теперь у вас с Секретной белкой одинаковый общий секретный цвет. Вы успешно поделились секретной информацией с совершенно незнакомым человеком. Это удивительно точно отражает принцип работы криптографии с открытым ключом. Другим распространенным названием для этой последовательности событий является обмен ключами Диффи-Хеллмана. Обмен ключами состоит из следующих этапов:

  • Закрытый ключ - это ваш личный цвет из примеров.
  • Открытый ключ - это комбинированный цвет, которым вы поделились.

Закрытый ключ - это то, что вы всегда держите в секрете, в то время как открытым ключом можно поделиться с кем угодно. Эти концепции напрямую связаны с реальным миром HTTPS-приложений на Python. Теперь, когда у сервера и клиента есть общий секрет, вы можете использовать свое старое симметричное шифрование pal для шифрования всех дальнейших сообщений!

Примечание: Криптография с открытым ключом также использует некоторые математические методы для смешивания цветов. На странице Википедии для обмена ключами Диффи-Хеллмана есть хорошее объяснение, но подробное объяснение выходит за рамки данного руководства.

Когда вы общаетесь через защищенный веб-сайт, подобный этому, ваш браузер и сервер устанавливают безопасную связь, используя те же принципы:

  1. Ваш браузер запрашивает информацию с сервера.
  2. Ваш браузер и сервер обмениваются открытыми ключами.
  3. Ваш браузер и сервер генерируют общий закрытый ключ.
  4. Ваш браузер и сервер шифруют и расшифровывают сообщения, используя этот общий ключ, посредством симметричного шифрования.

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

На что похож HTTPS в реальном мире?

Учитывая всю эту информацию о шифровании, давайте немного уменьшим масштаб и поговорим о том, как на самом деле работают HTTPS-приложения на Python в реальном мире. Шифрование - это только половина дела. При посещении защищенного веб-сайта необходимы два основных компонента:

  1. Шифрование преобразует открытый текст в зашифрованный текст и обратно.
  2. Аутентификация подтверждает, что человек или вещь являются теми, за кого они себя выдают.

Вы много слышали о том, как работает шифрование, но как насчет аутентификации? Чтобы понять, как работает аутентификация в реальном мире, вам необходимо знать об инфраструктуре открытых ключей . PKI привносит в экосистему безопасности еще одну важную концепцию, называемую сертификатами.

Сертификаты подобны паспортам для Интернета. Как и большинство вещей в компьютерном мире, они представляют собой просто фрагменты данных в файле. Вообще говоря, сертификаты содержат следующую информацию:

  • Выдан: указывает, кому принадлежит сертификат
  • Выдано: указывает, кто выдал сертификат
  • Срок действия: определяет период времени, в течение которого сертификат действителен

Как и паспорта, сертификаты действительно полезны только в том случае, если они сгенерированы и признаны каким-либо органом. Вашему браузеру нецелесообразно знать о каждом сертификате каждого сайта, который вы посещаете в Интернете. Вместо этого PKI опирается на концепцию, известную как Центры сертификации (CA).

Центры сертификации отвечают за выдачу сертификатов. В PKI они считаются доверенной третьей стороной (TTP). По сути, эти организации выступают в качестве действительных центров сертификации. Предположим, вы хотели бы посетить другую страну, и у вас есть паспорт со всеми вашими данными. Как сотрудники иммиграционной службы в другой стране узнают, что в вашем паспорте указаны действительные данные?

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

Другой способ справиться с этим - отправить всю вашу информацию в Доверенную третью сторону (TTP). Служба TTP тщательно изучит предоставленную вами информацию, проверит ваши заявления, а затем подпишет ваш паспорт. Это оказывается гораздо более практичным, поскольку сотрудникам иммиграционной службы необходимо знать только доверенных третьих лиц.

На практике сертификаты обрабатываются в соответствии со сценарием TTP. Процесс выглядит примерно так:

  1. Создайте запрос на подписание сертификата (CSR): Это похоже на заполнение информации для вашей визы.
  2. Отправьте CSR доверенной третьей стороне (TTP): Это похоже на отправку вашей информации в визовый центр.
  3. Проверьте вашу информацию: Каким-то образом TTP должен подтвердить предоставленную вами информацию. В качестве примера смотрите как Amazon подтверждает право собственности.
  4. Сгенерируйте открытый ключ: TTP подписывает ваш CSR. Это эквивалентно тому, что TTP подписывает вашу visa.
  5. Выдайте подтвержденный открытый ключ: Это эквивалентно получению вами визы по почте.

Обратите внимание, что CSR криптографически привязан к вашему закрытому ключу. Таким образом, все три элемента информации — открытый ключ, закрытый ключ и центр сертификации — так или иначе связаны между собой. Это создает так называемую цепочку доверия , так что теперь у вас есть действительный сертификат, который можно использовать для подтверждения вашей личности.

Чаще всего за это отвечают только владельцы веб-сайтов. Владелец веб-сайта выполнит все эти действия. В конце этого процесса в его сертификате будет указано следующее:

От времени A до времени B Я X согласно Y

Это предложение - все, что на самом деле сообщает вам сертификат. Переменные могут быть заполнены следующим образом:

  • - это действительные дата и время начала.
  • B - действительные дата и время окончания.
  • X - это имя сервера.
  • Y - это название центра сертификации.

По сути, это все, что описывает сертификат. Другими словами, наличие сертификата не обязательно означает, что вы тот, за кого себя выдаете, просто вы должны Y согласиться с тем, что вы тот, за кого себя выдаете. Вот тут-то и возникает “доверенная” часть доверенных третьих сторон.

Чтобы все были довольны протоколом HTTPS, между клиентами и серверами должен быть общий доступ к протоколам TTP. В вашем браузере автоматически устанавливается множество центров сертификации. Чтобы просмотреть их, выполните следующие действия:

  • Chrome: Перейдите в раздел Настройки > Дополнительно > Конфиденциальность и безопасность > Управление сертификатами > Полномочия.
  • Firefox: Перейдите в раздел Настройки > Предпочтения > Конфиденциальность и безопасность > Просмотр сертификатов > Полномочия.

Здесь описывается инфраструктура, необходимая для создания HTTPS-приложений на Python в реальном мире. В следующем разделе вы примените эти концепции к своему собственному коду. Вы ознакомитесь с наиболее распространенными примерами и станете собственным центром сертификации Secret Squirrels!

Как выглядит HTTPS-приложение на Python?

Теперь, когда у вас есть представление об основных элементах, необходимых для создания HTTPS-приложения на Python, пришло время связать все элементы вместе, один за другим, с вашим предыдущим приложением. Это обеспечит безопасность вашего взаимодействия между сервером и клиентом.

Возможно настроить всю инфраструктуру PKI на вашем собственном компьютере, и это именно то, что вы будете делать в этом разделе. Это не так сложно, как кажется, так что не волнуйтесь! Стать настоящим центром сертификации значительно сложнее, чем выполнить описанные ниже действия, но то, что вы прочтете, - это более или менее все, что вам нужно для запуска вашего собственного центра сертификации.

Как стать центром сертификации

Центр сертификации - это не что иное, как очень важная пара открытых и закрытых ключей. Чтобы стать центром сертификации, вам просто нужно сгенерировать пару открытых и закрытых ключей.

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

Вашей начальной парой открытого и закрытого ключей будет самозаверяющий сертификат. Вы создаете первоначальный секрет, поэтому, если вы действительно собираетесь стать центром сертификации, невероятно важно, чтобы этот закрытый ключ был безопасным. Если кто-то получит доступ к паре открытого и закрытого ключей центра сертификации, он сможет сгенерировать полностью действительный сертификат, и вы ничего не сможете сделать, чтобы обнаружить проблему, кроме как перестать доверять своему центру сертификации.

После устранения этого предупреждения вы сможете сгенерировать сертификат в кратчайшие сроки. Для начала вам нужно сгенерировать закрытый ключ. Вставьте следующее в файл с именем pki_helpers.py:

 1# pki_helpers.py
 2from cryptography.hazmat.backends import default_backend
 3from cryptography.hazmat.primitives import serialization
 4from cryptography.hazmat.primitives.asymmetric import rsa
 5
 6def generate_private_key(filename: str, passphrase: str):
 7    private_key = rsa.generate_private_key(
 8        public_exponent=65537, key_size=2048, backend=default_backend()
 9    )
10
11    utf8_pass = passphrase.encode("utf-8")
12    algorithm = serialization.BestAvailableEncryption(utf8_pass)
13
14    with open(filename, "wb") as keyfile:
15        keyfile.write(
16            private_key.private_bytes(
17                encoding=serialization.Encoding.PEM,
18                format=serialization.PrivateFormat.TraditionalOpenSSL,
19                encryption_algorithm=algorithm,
20            )
21        )
22
23    return private_key

generate_private_key() генерирует закрытый ключ, используя RSA. Вот расшифровка кода:

  • Строки со 2 по 4 импортируйте библиотеки, необходимые для работы функции.
  • В строках с 7 по 9 используется RSA для генерации закрытого ключа. Магические числа 65537 и 2048 - это всего лишь два возможных значения. Вы можете прочитать больше о том, почему , или просто поверить, что эти цифры полезны.
  • Строки с 11 по 12 настраивают алгоритм шифрования, который будет использоваться для вашего закрытого ключа.
  • Строки с 14 по 21 запишите свой закрытый ключ на диск в указанном filename месте. Этот файл зашифрован с использованием предоставленного пароля.

Следующим шагом на пути к тому, чтобы стать вашим собственным центром сертификации, является создание самоподписанного открытого ключа. Вы можете обойти запрос на подпись сертификата (CSR) и сразу же создать открытый ключ. Вставьте следующее в pki_helpers.py:

 1# pki_helpers.py
 2from datetime import datetime, timedelta
 3from cryptography import x509
 4from cryptography.x509.oid import NameOID
 5from cryptography.hazmat.primitives import hashes
 6
 7def generate_public_key(private_key, filename, **kwargs):
 8    subject = x509.Name(
 9        [
10            x509.NameAttribute(NameOID.COUNTRY_NAME, kwargs["country"]),
11            x509.NameAttribute(
12                NameOID.STATE_OR_PROVINCE_NAME, kwargs["state"]
13            ),
14            x509.NameAttribute(NameOID.LOCALITY_NAME, kwargs["locality"]),
15            x509.NameAttribute(NameOID.ORGANIZATION_NAME, kwargs["org"]),
16            x509.NameAttribute(NameOID.COMMON_NAME, kwargs["hostname"]),
17        ]
18    )
19
20    # Because this is self signed, the issuer is always the subject
21    issuer = subject
22
23    # This certificate is valid from now until 30 days
24    valid_from = datetime.utcnow()
25    valid_to = valid_from + timedelta(days=30)
26
27    # Used to build the certificate
28    builder = (
29        x509.CertificateBuilder()
30        .subject_name(subject)
31        .issuer_name(issuer)
32        .public_key(private_key.public_key())
33        .serial_number(x509.random_serial_number())
34        .not_valid_before(valid_from)
35        .not_valid_after(valid_to)
36        .add_extension(x509.BasicConstraints(ca=True,
37            path_length=None), critical=True)
38    )
39
40    # Sign the certificate with the private key
41    public_key = builder.sign(
42        private_key, hashes.SHA256(), default_backend()
43    )
44
45    with open(filename, "wb") as certfile:
46        certfile.write(public_key.public_bytes(serialization.Encoding.PEM))
47
48    return public_key

Здесь у вас есть новая функция generate_public_key(), которая сгенерирует самоподписанный открытый ключ. Вот как работает этот код:

  • Строки со 2 по 5 - это импорт, необходимый для работы функции.
  • Строки с 8 по 18 содержат информацию о предмете сертификата.
  • В строке 21 используются один и тот же эмитент и субъект, поскольку это самозаверяющий сертификат.
  • В строках с 24 по 25 указан период времени, в течение которого этот открытый ключ действителен. В данном случае это 30 дней.
  • Строки с 28 по 38 добавляют всю необходимую информацию в объект конструктора открытых ключей, который затем необходимо подписать.
  • Строки с 41 по 43 подпишите открытый ключ закрытым ключом.
  • Строки с 45 по 46 запишите открытый ключ в filename.

Используя эти две функции, вы можете довольно быстро сгенерировать свою пару секретных и открытых ключей в Python:

>>> from pki_helpers import generate_private_key, generate_public_key
>>> private_key = generate_private_key("ca-private-key.pem", "secret_password")
>>> private_key
<cryptography.hazmat.backends.openssl.rsa._RSAPrivateKey object at 0x7ffbb292bf90>
>>> generate_public_key(
...   private_key,
...   filename="ca-public-key.pem",
...   country="US",
...   state="Maryland",
...   locality="Baltimore",
...   org="My CA Company",
...   hostname="my-ca.com",
... )
<Certificate(subject=<Name(C=US,ST=Maryland,L=Baltimore,O=My CA Company,CN=logan-ca.com)>, ...)>

После импорта вспомогательных функций из pki_helpers вы сначала генерируете свой закрытый ключ и сохраняете его в файле ca-private-key.pem. Затем вы передаете этот закрытый ключ в generate_public_key(), чтобы сгенерировать свой открытый ключ. Теперь в вашем каталоге должны быть два файла:

$ ls ca*
ca-private-key.pem ca-public-key.pem

Поздравляем! Теперь у вас есть возможность быть Центром сертификации.

Доверие к Вашему серверу

Первым шагом к тому, чтобы ваш сервер стал надежным, является создание Запроса на подпись сертификата (CSR). В реальном мире CSR был бы отправлен в реальный центр сертификации, такой как Verisign или Let's Encrypt. В этом примере вы будете использовать только что созданный центр сертификации.

Вставьте код для генерации CSR в pki_helpers.py файл, указанный выше:

 1# pki_helpers.py
 2def generate_csr(private_key, filename, **kwargs):
 3    subject = x509.Name(
 4        [
 5            x509.NameAttribute(NameOID.COUNTRY_NAME, kwargs["country"]),
 6            x509.NameAttribute(
 7                NameOID.STATE_OR_PROVINCE_NAME, kwargs["state"]
 8            ),
 9            x509.NameAttribute(NameOID.LOCALITY_NAME, kwargs["locality"]),
10            x509.NameAttribute(NameOID.ORGANIZATION_NAME, kwargs["org"]),
11            x509.NameAttribute(NameOID.COMMON_NAME, kwargs["hostname"]),
12        ]
13    )
14
15    # Generate any alternative dns names
16    alt_names = []
17    for name in kwargs.get("alt_names", []):
18        alt_names.append(x509.DNSName(name))
19    san = x509.SubjectAlternativeName(alt_names)
20
21    builder = (
22        x509.CertificateSigningRequestBuilder()
23        .subject_name(subject)
24        .add_extension(san, critical=False)
25    )
26
27    csr = builder.sign(private_key, hashes.SHA256(), default_backend())
28
29    with open(filename, "wb") as csrfile:
30        csrfile.write(csr.public_bytes(serialization.Encoding.PEM))
31
32    return csr

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

  • Строки с 16 по 19 задайте альтернативные DNS-имена, которые будут действительны для вашего сертификата.
  • Строки с 21 по 25 генерируют другой объект конструктора, но применяется тот же фундаментальный принцип, что и раньше. Вы создаете все необходимые атрибуты для вашего CSR.
  • Строка 27 подписывает ваш CSR с помощью закрытого ключа.
  • Строки с 29 по 30 запишите свой CSR на диск в формате PEM.

Вы заметите, что для создания CSR вам сначала понадобится закрытый ключ. К счастью, вы можете использовать тот же generate_private_key(), что и при создании закрытого ключа вашего центра сертификации. Используя описанную выше функцию и предыдущие определенные методы, вы можете выполнить следующее:

>>> from pki_helpers import generate_csr, generate_private_key
>>> server_private_key = generate_private_key(
...   "server-private-key.pem", "serverpassword"
... )
>>> server_private_key
<cryptography.hazmat.backends.openssl.rsa._RSAPrivateKey object at 0x7f6adafa3050>
>>> generate_csr(
...   server_private_key,
...   filename="server-csr.pem",
...   country="US",
...   state="Maryland",
...   locality="Baltimore",
...   org="My Company",
...   alt_names=["localhost"],
...   hostname="my-site.com",
... )
<cryptography.hazmat.backends.openssl.x509._CertificateSigningRequest object at 0x7f6ad5372210>

После выполнения этих действий в консоли у вас должны появиться два новых файла:

  1. server-private-key.pem: закрытый ключ вашего сервера
  2. server-csr.pem: CSR вашего сервера

Вы можете просмотреть свой новый CSR и закрытый ключ в консоли:

$ ls server*.pem
server-csr.pem  server-private-key.pem

Имея на руках эти два документа, вы можете приступить к процессу подписания ваших ключей. Как правило, на этом этапе выполняется много проверок. В реальном мире центр сертификации убедился бы, что вы являетесь владельцем my-site.com, и попросил бы вас доказать это различными способами.

Поскольку в данном случае вы являетесь центром сертификации, вы можете избежать этой головной боли и создать свой собственный проверенный открытый ключ. Для этого вы добавите еще одну функцию в свой файл pki_helpers.py:

 1# pki_helpers.py
 2def sign_csr(csr, ca_public_key, ca_private_key, new_filename):
 3    valid_from = datetime.utcnow()
 4    valid_until = valid_from + timedelta(days=30)
 5
 6    builder = (
 7        x509.CertificateBuilder()
 8        .subject_name(csr.subject)
 9        .issuer_name(ca_public_key.subject)
10        .public_key(csr.public_key())
11        .serial_number(x509.random_serial_number())
12        .not_valid_before(valid_from)
13        .not_valid_after(valid_until)
14    )
15
16    for extension in csr.extensions:
17        builder = builder.add_extension(extension.value, extension.critical)
18
19    public_key = builder.sign(
20        private_key=ca_private_key,
21        algorithm=hashes.SHA256(),
22        backend=default_backend(),
23    )
24
25    with open(new_filename, "wb") as keyfile:
26        keyfile.write(public_key.public_bytes(serialization.Encoding.PEM))

Этот код выглядит очень похоже на generate_public_key() из файла generate_ca.py. На самом деле, они почти идентичны. Основные отличия заключаются в следующем:

  • В строках с 8 по 9 имя субъекта указано на основе CSR, в то время как имя эмитента указано на основе Центра сертификации.
  • Строка 10 на этот раз получает открытый ключ от CSR. Строка в generate_public_key(), указывающая, что это центр сертификации, в конце определения builder была удалена.
  • Строки с 16 по 17 скопируйте все расширения, которые были установлены в CSR.
  • Строка 20 подписывает открытый ключ закрытым ключом центра сертификации.

Следующим шагом будет запуск консоли Python и использование sign_csr(). Вам нужно будет загрузить свой CSR и закрытый и открытый ключи вашего центра сертификации. Начните с загрузки вашего CSR:

>>> from cryptography import x509
>>> from cryptography.hazmat.backends import default_backend
>>> csr_file = open("server-csr.pem", "rb")
>>> csr = x509.load_pem_x509_csr(csr_file.read(), default_backend())
>>> csr
<cryptography.hazmat.backends.openssl.x509._CertificateSigningRequest object at 0x7f68ae289150>

В этом разделе кода вы открываете свой файл server-csr.pem и используете x509.load_pem_x509_csr() для создания своего объекта csr. Далее вам нужно будет загрузить открытый ключ вашего центра сертификации:

>>> ca_public_key_file = open("ca-public-key.pem", "rb")
>>> ca_public_key = x509.load_pem_x509_certificate(
...   ca_public_key_file.read(), default_backend()
... )
>>> ca_public_key
<Certificate(subject=<Name(C=US,ST=Maryland,L=Baltimore,O=My CA Company,CN=logan-ca.com)>, ...)>

В очередной раз вы создали объект ca_public_key, который может быть использован sign_csr(). В модуле x509 есть удобный инструмент load_pem_x509_certificate(), который поможет. Последним шагом является загрузка закрытого ключа вашего центра сертификации:

>>> from getpass import getpass
>>> from cryptography.hazmat.primitives import serialization
>>> ca_private_key_file = open("ca-private-key.pem", "rb")
>>> ca_private_key = serialization.load_pem_private_key(
...   ca_private_key_file.read(),
...   getpass().encode("utf-8"),
...   default_backend(),
... )
Password:
>>> private_key
<cryptography.hazmat.backends.openssl.rsa._RSAPrivateKey object at 0x7f68a85ade50>

Этот код загрузит ваш закрытый ключ. Напомним, что ранее ваш закрытый ключ был зашифрован с использованием указанного вами пароля. С помощью этих трех компонентов вы теперь можете подписать свой CSR и сгенерировать подтвержденный открытый ключ:

>>> from pki_helpers import sign_csr
>>> sign_csr(csr, ca_public_key, ca_private_key, "server-public-key.pem")

После запуска этого приложения в вашем каталоге должны быть три файла ключей сервера:

$ ls server*.pem
server-csr.pem  server-private-key.pem  server-public-key.pem

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

Используя исходный файл server.py, запустите следующую команду, чтобы запустить совершенно новое HTTPS-приложение на Python:

$ uwsgi \
    --master \
    --https localhost:5683,\
            logan-site.com-public-key.pem,\
            logan-site.com-private-key.pem \
    --mount /=server:app

Поздравляем! Теперь у вас есть сервер с поддержкой HTTPS на Python, работающий с вашей собственной парой открытых и закрытых ключей, которая была подписана вашим собственным центром сертификации!

Примечание: Существует еще одна сторона уравнения аутентификации Python по протоколу HTTPS, и это клиент. Также можно настроить проверку сертификата для клиентского сертификата. Это требует немного больше усилий и практически не используется за пределами предприятий. Однако проверка подлинности клиента может быть очень мощным инструментом.

Теперь все, что осталось сделать, это запросить ваш сервер. Сначала вам нужно внести некоторые изменения в код client.py:

# client.py
import os
import requests

def get_secret_message():
    response = requests.get("https://localhost:5683")
    print(f"The secret message is {response.text}")

if __name__ == "__main__":
    get_secret_message()

Единственное изменение по сравнению с предыдущим кодом - с http на https. Если вы попытаетесь запустить этот код, то получите сообщение об ошибке:

$ python client.py
...
requests.exceptions.SSLError: \
    HTTPSConnectionPool(host='localhost', port=5683): \
    Max retries exceeded with url: / (Caused by \
    SSLError(SSLCertVerificationError(1, \
    '[SSL: CERTIFICATE_VERIFY_FAILED] \
    certificate verify failed: unable to get local issuer \
    certificate (_ssl.c:1076)')))

Это довольно неприятное сообщение об ошибке! Важной частью здесь является сообщение certificate verify failed: unable to get local issuer. Теперь эти слова должны быть вам более знакомы. По сути, в нем говорится следующее:

локальный хостинг:5683 выдал мне сертификат. Я проверил эмитента сертификата, который он мне выдал, и, согласно всем известным мне центрам сертификации, этот эмитент не входит в их число.

Если вы попытаетесь перейти на свой веб-сайт с помощью браузера, то получите аналогичное сообщение:

Chrome certificate warning

Если вы хотите избежать появления этого сообщения, вам необходимо сообщить requests о вашем центре сертификации! Все, что вам нужно сделать, это направить запросы в файл ca-public-key.pem, который вы сгенерировали ранее:

# client.py
def get_secret_message():
    response = requests.get("http://localhost:5683", verify="ca-public-key.pem")
    print(f"The secret message is {response.text}")

После этого вы сможете успешно выполнить следующее:

$ python client.py
The secret message is fluffy tail

Отлично! Вы создали полнофункциональный HTTPS-сервер на Python и успешно выполнили запрос к нему. Теперь у вас и секретных белок есть сообщения, которыми вы можете обмениваться безопасно и с удовольствием!

Заключение

В этом руководстве вы ознакомились с некоторыми основными принципами безопасной коммуникации в современном Интернете. Теперь, когда вы понимаете эти базовые элементы, вы станете лучшим и более защищенным разработчиком.

На протяжении всего этого урока вы получили представление о нескольких темах:

  • Криптография
  • HTTPS и TLS
  • Инфраструктура открытых ключей
  • Сертификаты

Если эта информация вас заинтересовала, то вам повезло! Вы лишь поверхностно изучили все нюансы, связанные с каждым уровнем безопасности. Мир безопасности постоянно развивается, и постоянно обнаруживаются новые методы и уязвимости.

Back to Top