Всем доброго времени суток. Мне необходимо проверить, что конкретный файл (.pdf) подписан конкретной открепленной эцп (.sig).
Я перепробовал множество вариантов, но все они выдают, что подпись недействительна или то, что хэш файла и подписи различаются.
Я использую asn1crypto и pygost (3.16, версии новее так же пробовал, но нужного результата не получил)
Пробовал использовать советы и коммиты из данного обсуждения:
https://github.com/wbond/asn1crypto/issues/120.
Но они так же не дали результата.
Вот мой код:
import os
from asn1crypto import cms, core
from pygost import gost3410, gost34112012
import hashlib
def get_curve_from_cert(cert):
try:
pub_key_algorithm = cert['tbs_certificate']['subject_public_key_info']['algorithm']
algorithm_oid = pub_key_algorithm['algorithm'].dotted
if "1.2.643.7.1.1.1.1" in algorithm_oid: # ГОСТ Р 34.10-2012 256
return 'GostR3410_2012_TC26_256_ParamSetA'
elif "1.2.643.7.1.1.1.2" in algorithm_oid: # ГОСТ Р 34.10-2012 512
return 'GostR3410_2012_TC26_ParamSetA' # 512-битная версия
else:
return 'GostR3410_2012_TC26_256_ParamSetA'
except Exception as e:
return 'GostR3410_2012_TC26_256_ParamSetA'
def extract_public_key_correctly(pub_key_bytes):
if len(pub_key_bytes) == 66 and pub_key_bytes[0] == 0x04:
key_data = pub_key_bytes[1:]
if len(key_data) == 65:
key_data = key_data[1:]
else:
key_data = pub_key_bytes
if len(key_data) != 64:
raise ValueError(f"Ожидается 64 байта ключа, получено {len(key_data)}")
x_bytes = key_data[:32]
y_bytes = key_data[32:]
x = int.from_bytes(x_bytes, "little")
y = int.from_bytes(y_bytes, "little")
return x, y
def compute_signed_attributes_hash(signed_attrs):
attrs_for_hash = signed_attrs.copy()
attrs_der = attrs_for_hash.dump()
if attrs_der[0] == 0xa0: # IMPLICIT [0]
attrs_der = b'\x31' + attrs_der[1:] # Заменяем на SET
return attrs_der
def verify_gost_detached(pdf_path: str, sig_path: str) -> bool:
with open(sig_path, "rb") as f:
sig_der = f.read()
with open(pdf_path, "rb") as f:
pdf_bytes = f.read()
content_info = cms.ContentInfo.load(sig_der)
signed_data = content_info["content"]
signer_info = signed_data["signer_infos"][0]
cert = signed_data["certificates"][0].chosen
curve_name = get_curve_from_cert(cert)
if curve_name is None:
raise ValueError("Не удалось определить подходящую кривую")
curve = gost3410.CURVE_PARAMS[curve_name]
pub_key_info = cert['tbs_certificate']['subject_public_key_info']
pub_key_bytes = pub_key_info['public_key'].native
x, y = extract_public_key_correctly(pub_key_bytes)
if signer_info["signed_attrs"]:
data_to_verify = compute_signed_attributes_hash(signer_info["signed_attrs"])
else:
data_to_verify = pdf_bytes
digest_algorithm = signer_info["digest_algorithm"]["algorithm"].dotted
hash_obj = gost34112012.GOST34112012(data_to_verify, digest_size=32)
message_hash = hash_obj.digest()
signature = signer_info["signature"].native
if len(signature) != 64:
raise ValueError(f"Ожидались 64 байта подписи для 256-битной кривой, получено {len(signature)} байт")
r_bytes = signature[:32]
s_bytes = signature[32:]
r = int.from_bytes(r_bytes, "little")
s = int.from_bytes(s_bytes, "little")
try:
is_valid = gost3410.verify(curve, (x, y), message_hash, (r, s))
return is_valid
except Exception as e:
try:
r = int.from_bytes(r_bytes, "big")
s = int.from_bytes(s_bytes, "big")
is_valid = gost3410.verify(curve, (x, y), message_hash, (r, s))
return is_valid
except Exception as e2:
return False
def debug_signature_info(sig_path: str):
with open(sig_path, "rb") as f:
sig_der = f.read()
content_info = cms.ContentInfo.load(sig_der)
signed_data = content_info["content"]
signer_info = signed_data["signer_infos"][0]
cert = signed_data["certificates"][0].chosen
try:
tbs_cert = cert['tbs_certificate']
pub_key_info = tbs_cert['subject_public_key_info']
pub_key_algorithm = pub_key_info['algorithm']
if 'parameters' in pub_key_algorithm and pub_key_algorithm['parameters']:
print(f"Параметры алгоритма: {pub_key_algorithm['parameters']}")
except Exception as e:
print(f"Ошибка при извлечении информации о ключе: {e}")
try:
if signer_info["signed_attrs"]:
for attr in signer_info["signed_attrs"]:
print(f" - {attr['type']}: {attr['values'][0] if attr['values'] else 'None'}")
else:
print("SignedAttributes отсутствуют")
except Exception as e:
print(f"Ошибка при анализе SignedAttributes: {e}")
try:
signature = signer_info["signature"].native
except Exception as e:
print(f"Ошибка при извлечении подписи: {e}")
if __name__ == "__main__":
pdf_file = os.path.join(
os.path.dirname(__file__),
"файл.pdf",
)
sig_file = os.path.join(
os.path.dirname(__file__),
"ЭЦП.sig",
)
debug_signature_info(sig_file)
try:
is_valid = verify_gost_detached(pdf_file, sig_file)
except Exception as e:
import traceback
traceback.print_exc()
<code>
Вот что немного дополнительной информации:
Размер подписи: 64 байт
OID алгоритма публичного ключа: 1.2.643.7.1.1.1.1
Используется кривая: GostR3410_2012_TC26_256_ParamSetA
Исходная длина ключа: 66 байт
Первые байты: 04400f263804e198e19150e1af370279
После удаления 0x04: 65 байт
После удаления второго служебного байта: 64 байт
...
Подписываются SignedAttributes
Алгоритм хеширования: 1.2.643.7.1.1.2.2
...
Длина хеша: 32 байт
Длина подписи: 64 байт
Ошибка при проверке подписи: Invalid signature length
Пробуем big-endian для подписи: r=0xa059accd562ca5..., s=0x885f3c245c76cb...
Ошибка с big-endian: Invalid signature length
Результат проверки подписи: НЕДЕЙСТВИТЕЛЬНА
Если у кого-то была подобная задача или решение, то буду крайне благодарен.