Безопасность в Python: Аспекты защиты авторизации пользователей

Как говорят в NHL, "Shut the door"

Безопасность приложения — одна из самых главных столпов разработки. При ограниченном функционале вы сможете иметь лояльное ядро аудитории, но при глобальной уязвимости в коде, влекущие утечку персональных данных — вы потеряете вообще всех пользователей. Задача архитектора, программиста, QA в том, чтобы свести этот риск к минимуму. Но нужно помнить — абсолютной безопасности не существует. В любом случае необходимо иметь представление о том, как устроена информационная безопасность в языке Python

Что мы можем подразумевать под безопасностью в приложении? Есть три основных аспекта:

Конфиденциальность (от англ. confidence — доверие) — предотвращение утечки (разглашения) информации.

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

Доступность — обеспечение доступа к информации и связанным с ней активам авторизованных пользователей по мере необходимости.

Хэширование

Хэширование — преобразование входного массива данных произвольной длины в выходную битовую строку фиксированной длины. Иначе это называется хэш-функциями ли функциями свертки, а их результаты — хэш-кодом, контрольной суммой или дайджестом сообщения (message digest). Каждый результат работы функции (хэш-код) — уникален. Чтобы создать хэш-код, он же дайджест используются алгоритмы хэширования. Самые популярные алгоритмы прямо сейчас: MD5 (относительно ненадежен), SHA-1 (относительно ненадежен), SHA-2, SHA-3 и наш ГОСТ Р 34.11-2012.

Для работы с хэшированием в питоне используются модуль hashlib для вычисления хэш-функции и модуль HMAC (читается как эйчмак) — для проверки целостности информации (это гарантия того, что данные не будут изменены в процессе передачи), модуль secrets для генерации псевдослучайных чисел и модуль getpass, чтобы создать безопасный ввод логина и пароля. Есть и более продвинутые варианты для работы с низкоуровневыми интерфейсами, криптографическими алгоритмами: симметричные шифры, дайджесты сообщений и функции вывода ключей, такие как PyCryptodome или Cryptography.

HMAC (hash-based message authentication code, код аутентификации (проверки подлинности) сообщений, использующий хэш-функции) — в криптографии один из механизмов проверки целостности информации, позволяющий гарантировать, что данные, передаваемые или хранящиеся в ненадежной среде, не были изменены посторонними лицами. Механизм HMAC использует MAC — стандарт, излагающий способ обмена данными и проверки целостности передаваемых данных с использованием секретного ключа. Два клиента, использующие HMAC, как правило, разделяют общий секретный ключ.

Безопасность в Python: Аспекты защиты авторизации пользователей

Обычно HMAC-аутентификация используется во внутренних системах обмена сообщениями и в межпроцессном взаимодействии. Например, если требуется написать систему, которая включает множество процессов, взаимодействующих на кластере из нескольких машин. Тогда можно использовать данный подход, чтобы гарантировать, что только разрешенные процессы могут подключаться к другим. Модуль muliprocessing внутри использует аутентификацию, основанную на HMAC, чтобы установить соединения с подпроцессами.

Простой пример хэширования пароля

Аутентификация соединения  такая штука, которая может не подразумевать последующего шифрования — все сообщения передаются в открытом виде по сети. Любой, кто подключиться к вашей сети, может перехватить нужный сетевой трафик. Этого нам не нужно, поэтому мы создаем объект хэш-суммы с набором байтов h = hashlib.md5(). Добавляем в неё данные для расчета этой самой суммы h.update(b’Code’). Можно вывести хэш-сумму print(h.hexdigest()). HEX здесь прописываем т.к. используется шестнадцатеричная система счисления. Чтобы вычислить хэш используется специальная конструкция pbkdf2_hmac, в которой указаны параметры — имя алгоритма шифрования (sha256), пароль в виде строки байтов (b’pswd’, наличие соли (b’salt’) и длина расширенного ключа (100000). Плюс такого подхода в том, что недопустимо изменение даже одного символа, т.е. принцип целостности информации тут сохраняется

Код:

import hashlib, binascii

# Наша хэш функция
# Создание объекта хэш-суммы
h = hashlib.md5()
# Добавление данных для расчета суммы - можно добавлять только строку байтов
h.update(b'Code')
# Вывод хэш-суммы
print(h.hexdigest())

#Парольный хэш
dk = hashlib.pbkdf2_hmac('sha256', b'pswd', b'salt', 100000)
# Вычисленное значение можно хранить в БД
print(binascii.hexlify(dk))

Результат выполнения:

Безопасность в Python: Аспекты защиты авторизации пользователей

Пример аутентификации пользователей

Как мы будем реализовать аутентификацию пользователей в Python: сначала на клиентской стороне вычисляется значения hmac и отправляется на сервер. После запроса сервер формирует генерацию рандомного сообщения и возвращается на клиента. Затем клиент вычисляет у себя на основе секрета и рандомного сообщения значение функции hmac и отправляется серверу. Функция hmac рассчитывается также и на сервере. В завершении сервер сравнивает данные и на основе этого анализа дает заключение по аутентификации пользователя. Посмотрим как это выглядит в практической плоскости.

Серверная сторона

Функция echo_server принимает подключения клиентов (accept) и запускает функцию echo_handler — она проводит аутентификацию клиента и отсылает его же запрос обратно (эхо) с помощью функции server_authenticate. В ней мы создаем случайное рандомное сообщение и отправляем клиенту. Вычисляем HCMA-функцию и сравниваем её с результатом функции, присланным от клиента. Если они совпадают — подключаем клиента, если нет — сокет закрывается

Код:

import hmac
import os

# hmac.new(key, msg=None, digestmod=None)
# key - byte-строка, представляющая ключ
# msg - сообщение, для которого нужно вычислить хэш
# digestmod - имя хэш-фукнции, которая будет применена для вычисления (sha-1, sha-256, ...)


# -------------- Функция аутентификации клиента на сервере --------------------
def server_authenticate(connection, secret_key):
    ''' Запрос аутентификаии клиента.
        сonnection - сетевое соединение (сокет);
        secret_key - ключ шифрования, известный клиенту и серверу
    '''
    # 1. Создаётся случайное послание и отсылается клиенту
    message = os.urandom(32)
    connection.send(message)

    # 2. Вычисляется HMAC-функция (локальный результат) от послания с использованием секретного ключа
    hash = hmac.new(secret_key, message)
    digest = hash.digest()

    # 3. Пришедший ответ от клиента сравнивается с локальным результатом HMAC
    response = connection.recv(len(digest))
    return hmac.compare_digest(digest, response)

# ---------------------- Эхо-сервер -----------------------------------------
from socket import socket, AF_INET, SOCK_STREAM

secret_key = b'our_secret_key'

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):
    ''' Эхо-сервер.
        "Слушает" указанный адрес и общается с клиентом через echo_handler. 
    '''
    sock = socket(AF_INET, SOCK_STREAM)
    sock.bind(address)
    sock.listen(5)
    while True:
        conn, addr = sock.accept()
        echo_handler(conn)


echo_server(('', 9999))        

 

Клиентская сторона

На клиентской части мы подключаемся к серверу (sock.connect) и отправляем сообщение аутентификации sock.send(b’Hello, my secure server!’). До того, как мы отослали серверу сообщению — проходим аутентификацию на основе секретного ключа client_authenticate(sock, secret_key). Принимаем сообщение от сервера и вычисляем hcma функцию, чтобы отправить обратно к серверу, который его сравнивает со своим результатом

Код:

import hmac
import os

def client_authenticate(connection, secret_key):
    ''' Аутентификация клиента на удаленном сервисе.
        Параметр connection - сетевое соединение (сокет);
        secret_key - ключ шифрования, известный клиенту и серверу
    '''
    # принимаем случайное послание от сервера
    message = connection.recv(32)
    # вычисляем HMAC-функцию
    # <hmac.HMAC object at 0x000000BACEF3D278>
    # b'?\xa0\x85\x94`\xb9[\xe8\x865\x97\xb6\x06\x1e\xefj'
    hash = hmac.new(secret_key, message)
    digest = hash.digest()
    # отправляем ответ серверу
    connection.send(digest)


from socket import socket, AF_INET, SOCK_STREAM

secret_key = b'our_secret_key'

sock = socket(AF_INET, SOCK_STREAM)
sock.connect(('localhost', 9999))

# проходим аутентификацию
client_authenticate(sock, secret_key)

sock.send(b'Hello, my secure server!')
resp = sock.recv(1024)
print('Сервер ответил: ', resp.decode())

Шифрование данных

Для шифрования данных в питоне использовалась библиотека PyCrypto. Но её забросили еще в далеком 2014 году, а на сцену вышел форк (ответвление) PyCryptodome (PyCryptoDomeEx). Для шифрования данных в PyCryptodome есть поддержка нескольких алгоритмов — блочные шифры: AES, DES, 3DES, Blowfish и поточные шифры: Salsa20, ChaCha20. Устанавливается форк с помощью команды pip install pycryptodomex.

В нашем примере в переменную plaintext вносим шифруемое сообщение, преобразовав его в байты. Функция _encrypt принимает на вход тот самый текст сообщения в байтах. В переменную cipher записывается конструкция AES.new. Она создает новый шифр с использованием этого алгоритма. AES — это специальный симметричный шифр. Обратите внимание, что наша длина сообщений — 16 байт (т.к. используется AES-128). Длина ключа ASCII должна быть 16 символов (1 символ = 8 бит = 1 байт, 16 символов = 16 байт)

Код:

 

import os
from binascii import hexlify
from Cryptodome.Cipher import AES

# шифруемое сообщение
plaintext = b'The rain in Spain'


def padding_text(text):
    ''' Выравнивание сообщения до длины кратной 16 байтам.
        В данном случае исходное сообщение дополняется пробелами.
    '''
    pad_len = (16 - len(text) % 16) % 16
    return text + b' ' * pad_len


def _encrypt(plaintext, key):
    ''' Шифрование сообщения plaintext ключом key.

        Атрибут iv - вектор инициализации для алгоритма шифрования.
        Если не задаётся явно при создании объекта-шифра, то генерируется случайно.
        Его следует добавить в качестве префикса к финальному шифру, 
        чтобы была возможность правильно расшифровать сообщение.
    '''
    cipher = AES.new(key, AES.MODE_CBC)
    ciphertext = cipher.iv + cipher.encrypt(plaintext)
    return ciphertext


def _decrypt(ciphertext, key):
    ''' Расшифровка шифра ciphertext ключом key

        Вектор инициализации берётся из исходного шифра. 
        Его длина для большинства режимов шифрования всегда 16 байт.
        Расшифровываться будет оставшаяся часть шифра.
    '''
    cipher = AES.new(key, AES.MODE_CBC, iv=ciphertext[:16])
    msg = cipher.decrypt(ciphertext[16:])
    return msg


# Осуществим шифрование сообщения алгоритмом AES
# key (строка байтов) - секретный ключ для симметричного шифрования.
# Ключ должен быть длиной 16 (AES-128), 24 (AES-192) или 32 (AES-256) байта.
key = b'Super Secret Key'

# Длина сообщения должна быть кратна 16, поэтому выполним выравнивание.
plaintext = padding_text(plaintext)

# Выполним шифрование
cipher = _encrypt(plaintext, key)
print(hexlify(cipher))

# Выполним расшифрование
msg = _decrypt(cipher, key)
print(msg)

Как вам статья?

Рейтинг
( Пока оценок нет )
Диджитал на минималках
Комментарии: 2
  1. organizing

    233307 556686Sweet site, super style and design , truly clean and use friendly . 541725

  2. Resources

    134203 695213I must spend a while learning much more or working out more. Thanks for magnificent info I was looking for this information for my mission. 653757

Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: