На этом шаге мы рассмотрим реализацию этой задачи.
Вы хотите реализовать простой способ аутентификации клиентов, соединяющихся с серверами в распределенной системе, но вам не нужны сложные решения типа SSL.
Простой, но при этом эффективный способ аутентификации может быть реализован путем хендшейка при соединении с использованием модуля hmac. Вот пример:
import hmac import os def client_authenticate(connection, secret_key): ''' Аутентифицирует клиента на удаленном сервере. connection представляет сетевое соединение. secret_key - это ключ, известный только клиенту и серверу. ''' message = connection.recv(32) hash = hmac.new(secret_key, message) digest = hash.digest() connection.send(digest) def server_authenticate(connection, secret_key): ''' Запрашивает аутетентификацию клиента. ''' message = os.urandom(32) connection.send(message) hash = hmac.new(secret_key, message) digest = hash.digest() response = connection.recv(len(digest)) return hmac.compare_digest(digest, response)
Основная идея в том, что до установки соединения сервер предоставляет клиенту сообщение, состоящее из случайных байтов (в данном случае они генерируются os.random()). И клиент, и сервер вычисляют криптографический хеш этих случайных данных, используя hmac и секретный ключ, известный только обеим сторонам. Клиент посылает вычисленный дайджест обратно на сервер, где они сравниваются, после чего принимается решение - принимать соединение или нет.
Сравнение получившихся дайджестов должно выполняться с помощью функции hmac.compare_digest(). Она написана таким образом, чтобы предотвратить атаки на основе анализа тайминга, и должна быть использована вместо стандартного оператора сравнения (==).
Чтобы использовать эти функции, вы должны включить их в существующий сетевой код или код системы обмена сообщениями. Например, серверный код с сокетами может выглядеть как-то так:
from socket import socket, AF_INET, SOCK_STREAM from auth import server_authenticate secret_key = b'peekaboo' def echo_handler(client_sock): if not server_authenticate(client_sock, secret_key): client_sock.close() return while True: msg = client_sock.recv(8192) if not msg: break client_sock.sendall(msg) def echo_server(address): s = socket(AF_INET, SOCK_STREAM) s.bind(address) s.listen(5) while True: c,a = s.accept() echo_handler(c) print('Echo server running on port 18000') echo_server(('', 18000))
В клиенте нужен примерно такой код:
from socket import socket, AF_INET, SOCK_STREAM from auth import client_authenticate secret_key = b'peekaboo' s = socket(AF_INET, SOCK_STREAM) s.connect(('localhost', 18000)) client_authenticate(s, secret_key) s.send(b'Hello World') resp = s.recv(1024) print('Got:', resp)
Обычно аутентификация на базе hmac используется для внутренних систем обмена сообщениями и взаимодействия между процессами. Например, если вы пишете систему, в которой общаются между собой процессы, запущенные на кластере, вы можете использовать этот подход, чтобы быть уверенным, что соединяться между собой могут только процессы, у которых есть соответствующие права. Например, HMAC-аутентификация используется библиотекой multiprocessing, когда она устанавливает сообщение между подпроцессами.
Важно подчеркнуть, что аутентификация соединения - это не то же самое, что шифрование. Последующая коммуникация по аутентифицированному соединению идет без шифрования и поэтому может быть видима всем, кто вклинится посередине и будет сниффить трафик (хотя секретный ключ, известный обеим сторонам, никогда не передается).
Алгоритм аутентификации, используемый hmac, базируется на криптографических функциях хеширования, таких как MD5 и SHA-1, и в деталях описан в IETF RFC 2104.
На следующем шаге мы рассмотрим добавление SSL в сетевые сервисы.