gostcrypto
и asn1crypto
для формирования структуры подписи CAdES-BES в соответствии со стандартами.В современном цифровом мире обеспечение подлинности и целостности электронных документов имеет первостепенное значение. Электронные подписи (ЭП) служат этой цели, предоставляя криптографические гарантии. CAdES (CMS Advanced Electronic Signatures) – это набор стандартов для расширенных электронных подписей, построенных на основе CMS (Cryptographic Message Syntax). CAdES-BES (Basic Electronic Signature) является базовым форматом, который подтверждает, что подпись была создана конкретным подписантом.
Отсоединенная подпись (detached signature) означает, что сама подпись хранится отдельно от подписываемых данных. Это особенно удобно, когда исходный файл велик или когда его нежелательно изменять. Использование алгоритмов ГОСТ Р (Государственный Стандарт Российской Федерации) обеспечивает соответствие российскому законодательству и криптографическим требованиям.
Это руководство покажет, как создать отсоединенную подпись CAdES-BES с использованием алгоритмов ГОСТ Р в Python, что позволит вам интегрировать надежные механизмы ЭП в ваши приложения.
Иллюстрация процесса создания и упаковки цифровой подписи.
Для создания подписи CAdES-BES ГОСТ Р необходимо понимать основные стандарты и компоненты, участвующие в процессе.
Российские стандарты ГОСТ Р играют ключевую роль в обеспечении криптографической защиты информации.
Этот стандарт определяет алгоритмы формирования и проверки электронной подписи на основе эллиптических кривых. Он пришел на смену ГОСТ Р 34.10-2001 и предлагает повышенный уровень безопасности. Для подписей CAdES-BES мы будем использовать именно этот стандарт.
Данный стандарт определяет алгоритм хеш-функции ("Стрибог"), используемый для вычисления хеш-значения (дайджеста) подписываемых данных. Дайджест затем подписывается с использованием ГОСТ Р 34.10-2012. Стандарт предусматривает два варианта длины хеш-значения: 256 бит и 512 бит.
CAdES-BES (CMS Advanced Electronic Signatures - Basic Electronic Signature) – это базовый уровень формата CAdES, определенный в ETSI TS 101 733. Он обеспечивает подтверждение того, что подпись была создана, и связывает ее с подписанными данными через хеш.
Это основная структура CMS, содержащая саму подпись и связанную информацию. В случае отсоединенной подписи, поле encapContentInfo
(инкапсулированное содержимое) не содержит исходные данные, а только указывает их тип.
Подписываемые атрибуты (SignedAttributes
) являются критически важной частью CAdES. Они подписываются вместе с хешем документа и включают как минимум:
content-type
: Идентификатор типа подписываемых данных.message-digest
: Хеш-значение подписываемых данных.signing-time
: Время создания подписи (необязательный, но рекомендуемый атрибут для BES).Подписание этих атрибутов защищает от некоторых видов атак, например, от подмены самого хеша.
При использовании отсоединенной подписи, файл подписи (часто с расширением .sig или .p7s) создается отдельно от исходного документа. Это позволяет:
Сертификация ГОСТ Р как символ соответствия российским стандартам криптографии.
Для создания отсоединенной подписи CAdES-BES с ГОСТ Р в Python мы воспользуемся комбинацией библиотек: gostcrypto
для криптографических операций ГОСТ и asn1crypto
для формирования ASN.1 структур, необходимых для CAdES/CMS.
Убедитесь, что у вас установлены следующие библиотеки:
pip install gostcrypto asn1crypto
SignedAttributes
): Создаем структуру, включающую тип контента, дайджест сообщения и время подписи.SignedAttributes
: Получаем хеш-сумму сериализованных подписываемых атрибутов, также используя ГОСТ Р 34.11-2012.SignerInfo
: Этот объект содержит информацию о подписанте, использованных алгоритмах, подписываемых атрибутах и самой подписи.SignedData
: Основная структура CMS, содержащая SignerInfo
и другую метаинформацию. Для отсоединенной подписи исходные данные не включаются.ContentInfo
и сохранение подписи: Финальная структура, готовая для сохранения в файл (обычно с расширением .p7s или .sig).
from datetime import datetime, timezone
import gostcrypto.gostsignature as gostsig
import gostcrypto.gosthash as gosthash
from gostcrypto import encoding # Может понадобиться для специфических кодировок ключей
from asn1crypto import cms, core, x509, algos, pem
# --- Параметры ---
FILE_TO_SIGN_PATH = 'document.txt' # Путь к подписываемому файлу
PRIVATE_KEY_PATH = 'private_key.pem' # Путь к файлу закрытого ключа ГОСТ (PEM формат)
CERTIFICATE_PATH = 'certificate.cer' # Путь к файлу сертификата ГОСТ (DER или PEM формат)
SIGNATURE_OUTPUT_PATH = 'document.txt.p7s' # Путь для сохранения файла отсоединенной подписи
# --- 1. Загрузка данных и ключей/сертификатов ---
try:
# Чтение файла для подписи
with open(FILE_TO_SIGN_PATH, 'rb') as f:
data_to_sign = f.read()
# Загрузка закрытого ключа ГОСТ Р 34.10-2012
# gostcrypto ожидает ключ в бинарном формате (DER) или специальном PEM с GOST параметрами
# Если ключ в PEM, его нужно предварительно обработать или убедиться, что gostcrypto его понимает
# Для примера, предположим, что ключ уже в подходящем формате или gostcrypto.load_private_key это обработает.
with open(PRIVATE_KEY_PATH, 'rb') as f:
private_key_pem_data = f.read()
# Пытаемся определить, PEM ли это, и извлечь DER-содержимое, если необходимо для gostcrypto
# gostcrypto.load_private_key может ожидать DER-байт или специфичный PEM.
# Этот шаг может потребовать адаптации в зависимости от формата вашего ключа.
# В данном примере мы предполагаем, что load_private_key справится с PEM или вы предоставите DER.
# Для простоты, если это PEM, используем encoding.pem_armor_decode, если он есть
try:
# Пробуем декодировать PEM, если это он
_, _, private_key_der_data = pem.unarmor(private_key_pem_data)
except ValueError:
# Если не PEM, считаем, что это DER
private_key_der_data = private_key_pem_data
private_key = gostsig.load_private_key(private_key_der_data) # Пароль, если ключ зашифрован
# Загрузка сертификата подписанта (для SignerIdentifier и включения в подпись)
with open(CERTIFICATE_PATH, 'rb') as f:
cert_data = f.read()
# asn1crypto.x509.Certificate.load может загружать PEM или DER автоматически
certificate = x509.Certificate.load(cert_data)
except FileNotFoundError as e:
print(f"Ошибка: Файл не найден - {e.filename}")
exit()
except Exception as e:
print(f"Ошибка при загрузке ключей/сертификата: {e}")
exit()
# --- 2. Вычисление хеша файла (ГОСТ Р 34.11-2012, 256 бит) ---
data_digest = gosthash.new('streebog256', data_to_sign).digest()
# --- 3. Формирование SignedAttributes ---
# Обязательные атрибуты для CAdES-BES: content-type, message-digest. signing-time рекомендуется.
signed_attrs = cms.CMSAttributes([
cms.CMSAttribute({
'type': cms.CMSAttributeType('content_type'),
'values': [cms.ContentType('data')] # OID for id-data: 1.2.840.113549.1.7.1
}),
cms.CMSAttribute({
'type': cms.CMSAttributeType('message_digest'),
'values': [data_digest]
}),
cms.CMSAttribute({
'type': cms.CMSAttributeType('signing_time'),
# Время должно быть в UTC и формате UTCTime или GeneralizedTime
'values': [core.UTCTime(datetime.now(timezone.utc))]
}),
# Можно добавить другие атрибуты, например, signing_certificate_v2 (ETSI TS 101 733 / RFC 5035)
# Для CAdES-BES часто ожидается ETSI-defined SigningCertificateV2 attribute
# cms.CMSAttribute({
# 'type': cms.CMSAttributeType('1.2.840.113549.1.9.16.2.47'), # id-aa-signingCertificateV2
# 'values': [...] # Требует структуры ESSCertIDv2
# })
])
# Сериализация SignedAttributes (в DER формате, SET OF) для последующего хеширования
# Важно: атрибуты должны быть закодированы как SET OF для хеширования
signed_attrs_der = signed_attrs.dump()
# --- 4. Хеширование SignedAttributes (ГОСТ Р 34.11-2012, 256 бит) ---
signed_attrs_hash = gosthash.new('streebog256', signed_attrs_der).digest()
# --- 5. Создание подписи хеша атрибутов (ГОСТ Р 34.10-2012) ---
# gostsig.sign подписывает переданные байты (в нашем случае хеш атрибутов)
raw_signature = gostsig.sign(private_key, signed_attrs_hash)
# --- 6. Формирование SignerInfo ---
signer_info = cms.SignerInfo({
'version': 'v1', # Версия в зависимости от типа sid (issuerAndSerialNumber -> v1, subjectKeyIdentifier -> v3)
'sid': cms.SignerIdentifier({
'issuer_and_serial_number': cms.IssuerAndSerialNumber({
'issuer': certificate.issuer,
'serial_number': certificate.serial_number
})
}),
'digest_algorithm': algos.DigestAlgorithm({'algorithm': '1.2.643.7.1.1.2.2'}), # OID ГОСТ Р 34.11-2012 (256) - Streebog256
'signed_attrs': signed_attrs, # Включаем сами атрибуты
'signature_algorithm': algos.SignedDigestAlgorithm({'algorithm': '1.2.643.7.1.1.1.1'}), # OID ГОСТ Р 34.10-2012 (256)
'signature': raw_signature,
# 'unsigned_attrs': опционально
})
# --- 7. Сборка SignedData ---
# Для отсоединенной подписи, encap_content_info.content не указывается
signed_data = cms.SignedData({
'version': 'v1', # или v3, v4, v5 в зависимости от используемых фич
'digest_algorithms': [algos.DigestAlgorithm({'algorithm': '1.2.643.7.1.1.2.2'})], # Алгоритмы хеширования, используемые подписантами
'encap_content_info': cms.EncapsulatedContentInfo({
'content_type': cms.ContentType('data'), # Тип исходных данных
# 'content': data_to_sign # НЕ включаем для отсоединенной подписи
}),
'certificates': [certificate], # Включаем сертификат подписанта
# 'crls': [], # Можно включить CRL
'signer_infos': [signer_info]
})
# --- 8. Обертывание в ContentInfo и сохранение ---
content_info = cms.ContentInfo({
'content_type': cms.ContentType('signed_data'), # OID for id-signedData: 1.2.840.113549.1.7.2
'content': signed_data
})
# Сохранение отсоединенной подписи в файл
with open(SIGNATURE_OUTPUT_PATH, 'wb') as f:
f.write(content_info.dump()) # Сериализация в DER формат
print(f"Отсоединенная подпись CAdES-BES ГОСТ Р успешно создана и сохранена в: {SIGNATURE_OUTPUT_PATH}")
gostcrypto
обычно работает с ключами в формате DER или специфическом PEM для ГОСТ. asn1crypto
может парсить сертификаты в DER или PEM формате.gosthash.new('streebog256', data_to_sign).digest()
вычисляется дайджест (хеш) содержимого файла по алгоритму ГОСТ Р 34.11-2012 (256-битная версия "Стрибог").SignedAttributes
: Создается ASN.1 структура cms.CMSAttributes
, содержащая обязательные для CAdES-BES атрибуты: тип контента (content_type
), дайджест сообщения (message_digest
), и время подписи (signing_time
). Эти атрибуты сериализуются в DER-формат (signed_attrs.dump()
), так как именно эта бинарная строка будет хешироваться и подписываться.SignedAttributes
: Сериализованные атрибуты хешируются тем же алгоритмом ГОСТ Р 34.11-2012.gostsig.sign(private_key, signed_attrs_hash)
использует загруженный закрытый ключ ГОСТ Р 34.10-2012 для подписи хеша атрибутов.SignerInfo
: Это ключевая структура, описывающая одного подписанта. Она включает:
sid
(Signer Identifier): Идентификатор подписанта, обычно через издателя и серийный номер сертификата (IssuerAndSerialNumber
).digest_algorithm
: OID алгоритма хеширования, использованного для SignedAttributes
(в нашем случае, ГОСТ Р 34.11-2012).signed_attrs
: Сами подписываемые атрибуты.signature_algorithm
: OID алгоритма подписи (ГОСТ Р 34.10-2012).signature
: Непосредственно цифровая подпись (байты).SignedData
: Эта структура "обертывает" всю информацию о подписи. Для отсоединенной подписи поле encap_content_info
не содержит самих данных (content
), а лишь указывает их тип (content_type: 'data'
). В SignedData
также включаются использованные алгоритмы хеширования, сертификат(ы) подписантов и массив структур SignerInfo
.ContentInfo
и сохранение: SignedData
упаковывается в финальную структуру ContentInfo
, которая затем сериализуется в DER-формат (content_info.dump()
) и сохраняется в файл. Этот файл и является отсоединенной электронной подписью.Важно: Представленный код является примером. Реальная работа с криптографическими ключами и сертификатами требует строгого соблюдения мер безопасности, включая безопасное хранение ключей и управление жизненным циклом сертификатов. Также может потребоваться более сложная обработка ошибок и адаптация под конкретные форматы ключей/сертификатов.
Чтобы лучше понять последовательность шагов при создании отсоединенной подписи CAdES-BES с использованием алгоритмов ГОСТ Р, рассмотрим следующую ментальную карту. Она иллюстрирует ключевые этапы, начиная от подготовки данных и заканчивая формированием файла подписи.
Эта схема наглядно демонстрирует все этапы, которые проходит система для генерации корректной отсоединенной подписи CAdES-BES ГОСТ Р.
При реализации поддержки ГОСТ и CAdES в Python разработчики могут столкнуться с выбором между различными библиотеками и подходами. Диаграмма ниже представляет сравнительный анализ нескольких условных подходов по ключевым критериям. Оценки носят субъективный характер и призваны дать общее представление.
Как видно из диаграммы, подход с использованием gostcrypto
и asn1crypto
предлагает хороший баланс между соответствием стандартам ГОСТ, возможностью реализации CAdES и доступностью (бесплатные open-source библиотеки), хотя и требует более глубокого понимания структур ASN.1. Коммерческие решения, такие как Chilkat (если они поддерживают ГОСТ в вашей конфигурации), могут предложить большую простоту использования и полноту CAdES "из коробки", но ценой лицензионных отчислений.
Идентификаторы объектов (Object Identifiers, OID) играют фундаментальную роль в структурах ASN.1, таких как CMS/CAdES. Они однозначно определяют алгоритмы, типы данных, атрибуты и другие элементы. Ниже приведена таблица с некоторыми ключевыми OID, релевантными для создания подписей ГОСТ Р CAdES-BES.
OID | Описание | Мнемоника / Ссылка на стандарт |
---|---|---|
1.2.643.7.1.1.2.2 |
Алгоритм хеширования ГОСТ Р 34.11-2012 (256 бит, "Стрибог") | id-tc26-gost3411-12-256 |
1.2.643.7.1.1.1.1 |
Алгоритм цифровой подписи ГОСТ Р 34.10-2012 (с 256-битными параметрами) | id-tc26-gost3410-12-256 |
1.2.840.113549.1.7.1 |
Тип содержимого "data" (произвольные данные) | id-data (PKCS#7/CMS) |
1.2.840.113549.1.7.2 |
Тип содержимого "signedData" (подписанные данные CMS) | id-signedData (PKCS#7/CMS) |
1.2.840.113549.1.9.3 |
Атрибут "contentType" (тип содержимого) | contentType (PKCS#9 / CMS) |
1.2.840.113549.1.9.4 |
Атрибут "messageDigest" (дайджест сообщения) | messageDigest (PKCS#9 / CMS) |
1.2.840.113549.1.9.5 |
Атрибут "signingTime" (время подписи) | signingTime (PKCS#9 / CMS) |
1.2.840.113549.1.9.16.2.47 |
Атрибут "id-aa-signingCertificateV2" (рекомендован для CAdES) | ETSI TS 101 733 / RFC 5035 |
Знание этих OID помогает при отладке и анализе структур электронных подписей, а также при работе с низкоуровневыми криптографическими библиотеками.
При работе с электронными подписями, особенно в контексте государственных стандартов, важно учитывать ряд аспектов для обеспечения безопасности и корректности.
Закрытые ключи должны храниться в строгой тайне и с использованием защищенных механизмов (например, аппаратные токены PKCS#11, защищенные хранилища ОС). Компрометация закрытого ключа ведет к возможности подделки подписей от имени владельца. Сертификаты открытых ключей должны быть выданы доверенными удостоверяющими центрами (УЦ) и своевременно обновляться.
В коде, работающем с криптографией, крайне важна тщательная обработка ошибок. Некорректные ключи, сертификаты, данные или ошибки в криптографических операциях могут привести к невалидным подписям или уязвимостям. Всегда проверяйте возвращаемые значения функций и обрабатывайте возможные исключения.
Убедитесь, что версии используемых библиотек (gostcrypto
, asn1crypto
и их зависимостей, таких как OpenSSL) совместимы между собой и поддерживают необходимые алгоритмы ГОСТ. Иногда для поддержки ГОСТ на уровне системы может потребоваться специальная сборка OpenSSL.
После создания подписи рекомендуется проводить ее проверку с использованием эталонных инструментов или сервисов валидации (например, сервисы УЦ, тестовые площадки государственных порталов, если они поддерживают CAdES-BES ГОСТ Р). Это поможет убедиться в корректности сформированной подписи и ее соответствии стандартам.
Обзор различных профилей цифровых подписей, включая CAdES, в контексте европейского регламента eIDAS. Подобные профили существуют и для российских стандартов.
asn1crypto
вместе с gostcrypto
?