Рубрики
Без рубрики

hmac – криптографическая подпись и проверка сообщений

Автор оригинала: Doug Hellmann.

Цель:

Модуль hmac реализует хеширование с ключом для аутентификации сообщений, так как описан в RFC 2104.

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

Предупреждение

Отказ от ответственности: я не эксперт по безопасности. Для получения полной информации о HMAC ознакомьтесь с RFC 2104 .

Подписание сообщений

Функция new () создает новый объект для вычисления подписи сообщения. В этом примере используется алгоритм хеширования MD5 по умолчанию.

hmac_simple.py

import hmac

digest_maker  hmac.new(b'secret-shared-key-goes-here')

with open('lorem.txt', 'rb') as f:
    while True:
        block  f.read(1024)
        if not block:
            break
        digest_maker.update(block)

digest  digest_maker.hexdigest()
print(digest)

При запуске код считывает файл данных и вычисляет для него подпись HMAC.

$ python3 hmac_simple.py

4bcb287e284f8c21e87e14ba2dc40b16

Альтернативные типы дайджеста

Хотя криптографическим алгоритмом по умолчанию для hmac является MD5, это не самый безопасный метод для использования. У хэшей MD5 есть некоторые недостатки, такие как коллизии (когда два разных сообщения создают один и тот же хеш). Алгоритм SHA-1 считается более сильным, и его следует использовать вместо него.

hmac_sha.py

import hmac
import hashlib

digest_maker  hmac.new(
    b'secret-shared-key-goes-here',
    b'',
    hashlib.sha1,
)

with open('hmac_sha.py', 'rb') as f:
    while True:
        block  f.read(1024)
        if not block:
            break
        digest_maker.update(block)

digest  digest_maker.hexdigest()
print(digest)

Функция new () принимает три аргумента. Первый – это секретный ключ, который должен совместно использоваться двумя конечными точками, которые обмениваются данными, чтобы оба конца могли использовать одно и то же значение. Второе значение – это исходное сообщение. Если содержание сообщения, которое необходимо аутентифицировать, невелико, например отметка времени или HTTP POST, все тело сообщения можно передать в new () вместо использования update () метод. Последний аргумент – это используемый дайджест-модуль. По умолчанию используется hashlib.md5 . В этом примере передается 'sha1' , в результате чего hmac использует hashlib.sha1

$ python3 hmac_sha.py

dcee20eeee9ef8a453453f510d9b6765921cf099

Двоичные дайджесты

В предыдущих примерах для создания дайджестов для печати использовался метод hexdigest () . Шестнадцатеричный файл – это другое представление значения, вычисляемого методом digest () , которое представляет собой двоичное значение, которое может включать непечатаемые символы, включая NUL . Некоторые веб-сервисы (Google checkout, Amazon S3) используют версию двоичного дайджеста в кодировке base64 вместо шестнадцатеричного.

hmac_base64.py

import base64
import hmac
import hashlib

with open('lorem.txt', 'rb') as f:
    body  f.read()

hash  hmac.new(
    b'secret-shared-key-goes-here',
    body,
    hashlib.sha1,
)

digest  hash.digest()
print(base64.encodebytes(digest))

Строка в кодировке base64 заканчивается новой строкой, которую часто нужно удалять при встраивании строки в заголовки http или другие контексты, чувствительные к форматированию.

$ python3 hmac_base64.py

Применение подписей сообщений

Аутентификация HMAC должна использоваться для любой общедоступной сетевой службы и в любое время, когда данные хранятся там, где важна безопасность. Например, при отправке данных через конвейер или сокет эти данные должны быть подписаны, а затем подпись должна быть проверена перед использованием данных. Приведенный здесь расширенный пример доступен в файле hmac_pickle.py .

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

hmac_pickle.py

import hashlib
import hmac
import io
import pickle
import pprint


def make_digest(message):
    "Return a digest for the message."
    hash  hmac.new(
        b'secret-shared-key-goes-here',
        message,
        hashlib.sha1,
    )
    return hash.hexdigest().encode('utf-8')


class SimpleObject:
    """Demonstrate checking digests before unpickling.
    """

    def __init__(self, name):
        self.name  name

    def __str__(self):
        return self.name

Затем создайте буфер BytesIO для представления сокета или канала. В примере используется наивный, но простой для анализа формат потока данных. Записываются дайджест и длина данных с новой строкой. Далее следует сериализованное представление объекта, созданное pickle. Настоящая система не хотела бы зависеть от значения длины, поскольку, если дайджест неверен, вероятно, неверна и длина. Некоторая последовательность терминатора, которая вряд ли появится в реальных данных, была бы более подходящей.

Затем пример программы записывает в поток два объекта. первый записывается с использованием правильного значения дайджеста.

# Simulate a writable socket or pipe with a buffer
out_s  io.BytesIO()

# Write a valid object to the stream:
#  digest\nlength\npickle
o  SimpleObject('digest matches')
pickled_data  pickle.dumps(o)
digest  make_digest(pickled_data)
header  b'%s %d\n' % (digest, len(pickled_data))
print('WRITING: {}'.format(header))
out_s.write(header)
out_s.write(pickled_data)

Второй объект записывается в поток с недопустимым дайджестом, полученным путем вычисления дайджеста для некоторых других данных вместо рассола.

# Write an invalid object to the stream
o  SimpleObject('digest does not match')
pickled_data  pickle.dumps(o)
digest  make_digest(b'not the pickled data at all')
header  b'%s %d\n' % (digest, len(pickled_data))
print('\nWRITING: {}'.format(header))
out_s.write(header)
out_s.write(pickled_data)

out_s.flush()

Теперь, когда данные находятся в буфере BytesIO , их можно снова прочитать. Начните с чтения строки данных с дайджестом и длиной данных. Затем прочтите оставшиеся данные, используя значение длины. pickle.load () может читать непосредственно из потока, но это предполагает наличие надежного потока данных, а эти данные еще недостаточно доверены, чтобы их можно было извлечь. Считывать рассол в виде строки из потока, фактически не распаковывая объект, безопаснее.

# Simulate a readable socket or pipe with a buffer
in_s  io.BytesIO(out_s.getvalue())

# Read the data
while True:
    first_line  in_s.readline()
    if not first_line:
        break
    incoming_digest, incoming_length  first_line.split(b' ')
    incoming_length  int(incoming_length.decode('utf-8'))
    print('\nREAD:', incoming_digest, incoming_length)

После того, как обработанные данные находятся в памяти, значение дайджеста можно пересчитать и сравнить с данными, считанными с помощью compare_digest () . Если дайджесты совпадают, можно доверять данным и распаковывать их.

incoming_pickled_data  in_s.read(incoming_length)

    actual_digest  make_digest(incoming_pickled_data)
    print('ACTUAL:', actual_digest)

    if hmac.compare_digest(actual_digest, incoming_digest):
        obj  pickle.loads(incoming_pickled_data)
        print('OK:', obj)
    else:
        print('WARNING: Data corruption')

Выходные данные показывают, что первый объект проверен, а второй, как и ожидалось, считается «поврежденным».

$ python3 hmac_pickle.py

WRITING: b'f49cd2bf7922911129e8df37f76f95485a0b52ca 69\n'

WRITING: b'b01b209e28d7e053408ebe23b90fe5c33bc6a0ec 76\n'

READ: b'f49cd2bf7922911129e8df37f76f95485a0b52ca' 69
ACTUAL: b'f49cd2bf7922911129e8df37f76f95485a0b52ca'
OK: digest matches

READ: b'b01b209e28d7e053408ebe23b90fe5c33bc6a0ec' 76
ACTUAL: b'2ab061f9a9f749b8dd6f175bf57292e02e95c119'
WARNING: Data corruption

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

Смотрите также