Как реализовать криптографическую подпись в Python

«Как реализовать криптографическую подпись в Python» — вопрос из категории Безопасность, который задают на 10% собеседований Python Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В Python криптографическая подпись реализуется по-разному в зависимости от задачи: для симметричной аутентификации (проверка целостности и автора с общим ключом) используется HMAC, а для асимметричной (цифровая подпись с парой ключей) — алгоритмы вроде RSA или ECDSA.

1. HMAC (Hash-based Message Authentication Code)

Используется, когда обе стороны имеют общий секретный ключ. Гарантирует, что сообщение не было изменено и было отправлено владельцем ключа. Реализуется через модуль hmac.

Пример с hmac и hashlib:

import hmac
import hashlib

# Общий секретный ключ (должен быть известен только отправителю и получателю)
secret_key = b'my-super-secret-key'
message = b'This is the message to sign.'

# Создание подписи
signature = hmac.new(secret_key, message, hashlib.sha256).hexdigest()

print(f"Сообщение: {message.decode()}")
print(f"HMAC-SHA256 подпись: {signature}")

# Проверка подписи на стороне получателя
# Получатель генерирует подпись с тем же ключом и сравнивает
# hmac.compare_digest используется для безопасного сравнения строк
# во избежание атак по времени
received_signature = signature
is_valid = hmac.compare_digest(
    hmac.new(secret_key, message, hashlib.sha256).hexdigest(),
    received_signature
)

print(f"Подпись верна: {is_valid}")

2. Цифровая подпись (RSA)

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

Для этого используется библиотека cryptography.

Пример с cryptography:

from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding, rsa
from cryptography.exceptions import InvalidSignature

# 1. Генерация пары ключей (в реальном приложении ключи хранятся, а не генерируются каждый раз)
private_key = rsa.generate_private_key(
    public_exponent=65537,
    key_size=2048
)
public_key = private_key.public_key()

message = b"This is a message for digital signature."

# 2. Подпись сообщения приватным ключом
signature = private_key.sign(
    message,
    padding.PSS(
        mgf=padding.MGF1(hashes.SHA256()),
        salt_length=padding.PSS.MAX_LENGTH
    ),
    hashes.SHA256()
)

# 3. Проверка подписи публичным ключом
try:
    public_key.verify(
        signature,
        message,
        padding.PSS(
            mgf=padding.MGF1(hashes.SHA256()),
            salt_length=padding.PSS.MAX_LENGTH
        ),
        hashes.SHA256()
    )
    print("Цифровая подпись верна.")
except InvalidSignature:
    print("Ошибка: цифровая подпись недействительна!")

Ключевые различия:

  • HMAC: Нужен общий секрет. Быстрее. Цель — аутентификация и целостность.
  • RSA/ECDSA: Используется пара ключей. Медленнее. Цель — авторство, целостность и неотказуемость (non-repudiation).