Задать вопрос
@sebastian_fym

Как проверить что файл подписан конкретной (открепленной) ЭЦП?

Всем доброго времени суток. Мне необходимо проверить, что конкретный файл (.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

Результат проверки подписи: НЕДЕЙСТВИТЕЛЬНА

Если у кого-то была подобная задача или решение, то буду крайне благодарен.
  • Вопрос задан
  • 58 просмотров
Подписаться 1 Простой 4 комментария
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы