Задать вопрос
@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()

Немного дополнительной информации:
Размер подписи: 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

Результат проверки подписи: НЕДЕЙСТВИТЕЛЬНА
  • Вопрос задан
  • 213 просмотров
Подписаться 1 Простой 4 комментария
Пригласить эксперта
Ваш ответ на вопрос

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

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