Как исправить ошибку «Не пройдена проверка подписи» в Честном Знаке при работе через API?

Программист 1С v8.3 (Обычные формы) 1С:Комплексная автоматизация Управленческий учет Торговля и дистрибуция
← На главную

При интеграции 1С с системой «Честный Знак» (ГИС МТ) разработчики часто сталкиваются с ситуацией, когда авторизация проходит успешно, токен получается, но при попытке отправить документ (например, отгрузку или приемку) система возвращает ошибку «Не пройдена проверка подписи». Особенно остро эта проблема проявляется после плановой смены сертификата электронной подписи (ЭЦП). Даже если подпись корректно работает в личном кабинете через браузер, API-запросы могут отклоняться из-за строгих требований к формату передаваемых данных и самой подписи.

В этой статье мы подробно разберем технические нюансы формирования подписи для Честного Знака, проанализируем примеры кода и выясним, как правильно подготовить данные для успешной проверки на стороне сервера ГИС МТ.

Почему возникает ошибка после смены сертификата?

Рассмотрим основные причины, по которым новая подпись может не проходить проверку через программный интерфейс:

  1. Несоответствие структуры JSON: При подписании документа через API система крайне чувствительна к любым изменениям в теле запроса. Если вы подписали одну строку, а в HTTPЗапрос передали ее же, но с добавлением лишнего пробела или переноса строки, хеш-сумма не совпадет, и подпись будет признана невалидной.
  2. Форматирование Base64: Платформа 1С при преобразовании двоичных данных в строку Base64 (особенно в старых версиях или при определенных настройках) может вставлять символы переноса строк (CRLF) каждые 72 символа. Честный Знак ожидает «чистую» строку без этих символов.
  3. Отсутствие необходимых атрибутов: Для корректной проверки подписи часто требуется наличие атрибута времени подписания (Signing Time) внутри самой структуры подписи.
  4. Тип подписи: Для большинства операций в API v3 требуется открепленная подпись в формате CAdES-BES.

Подготовка данных перед подписанием

Прежде чем вызывать функции криптографии, проанализируем процесс подготовки JSON. Важно помнить, что подписываться должен именно тот текст, который будет отправлен в поле product_document. Рассмотрим, как правильно очистить строку от «мусора», который может добавить 1С.

Для версий 1С:Предприятие 8.2 и некоторых конфигураций на 8.3 может потребоваться самописная функция очистки символов переноса строки. Выясним, как она выглядит:


Функция УбратьСимволы1013(СтрокаДляОчистки) Экспорт
    // Удаляем символы перевода строки (ПС) и возврата каретки (ВК)
    Результат = СтрЗаменить(СтрокаДляОчистки, Символы.ПС, "");
    Результат = СтрЗаменить(Результат, Символы.ВК, "");
    
    // В некоторых случаях 1С может добавлять BOM (Byte Order Mark) в начало строки
    // Если это происходит, его также необходимо удалить
    Возврат Результат;
КонецФункции

Проанализируем ситуацию: если вы используете метод Base64Строка(), обязательно пропускайте результат через подобную очистку, прежде чем вставлять его в итоговый пакет данных.

Поиск сертификата в хранилище

Для программной работы с подписью нам необходимо найти нужный сертификат по его отпечатку (Thumbprint). Разберем по шагам, как это реализовать через COM-объект CAdESCOM.Store. Это наиболее универсальный способ, работающий в операционных системах Windows с установленным КриптоПро CSP.


Функция ПолучитьСертификатПоОтпечатку(ОтпечатокСтр)
    Рез = Неопределено;
    
    // Константы для работы с CAPICOM/CAdESCOM
    CAPICOM_CURRENT_USER_STORE = 2; // Хранилище текущего пользователя
    CAPICOM_MY_STORE = "My";       // Ветка "Личное"
    CAPICOM_STORE_OPEN_READ_ONLY = 0;
    
    Попытка
        oStore = Новый COMОбъект("CAdESCOM.Store");
        oStore.Open(CAPICOM_CURRENT_USER_STORE, CAPICOM_MY_STORE, CAPICOM_STORE_OPEN_READ_ONLY);
        
        // Поиск сертификата по SHA1 хешу (отпечатку)
        CAPICOM_CERTIFICATE_FIND_SHA1_HASH = 0;
        НаденныеСертификаты = oStore.Certificates.Find(CAPICOM_CERTIFICATE_FIND_SHA1_HASH, ОтпечатокСтр);
        
        Если НаденныеСертификаты.Count > 0 Тогда
            Рез = НаденныеСертификаты.Item(1);
        КонецЕсли;
        
        oStore.Close();
    Исключение
        Сообщить("Ошибка при доступе к хранилищу сертификатов: " + ОписаниеОшибки());
    КонецПопытки;
    
    Возврат Рез;
КонецФункции

Реализация функции подписания документа

Теперь перейдем к самому ответственному этапу — созданию открепленной подписи. Рассмотрим пример функции, которая принимает текст в формате Base64, отпечаток сертификата и возвращает готовую подпись.

Важный момент: Обратите внимание на свойство ContentEncoding. Если данные передаются в Base64, нужно установить значение 1 (CADESCOM_BASE64_TO_BINARY), чтобы объект понимал, что текст нужно сначала декодировать перед хешированием.


Функция ПодписатьТекст(ТекстДляПодписи, sThumbprint, bDetached = Истина)
    // Константы типа подписи
    CADESCOM_CADES_TYPE = 1; // CAdES-BES
    CADESCOM_BASE64_TO_BINARY = 1; // Входные данные в Base64
    CAPICOM_AUTHENTICATED_ATTRIBUTE_SIGNING_TIME = 0; // Атрибут времени

    Попытка
        oSigner = Новый COMОбъект("CAdESCOM.CPSigner");
        oSigner.Certificate = ПолучитьСертификатПоОтпечатку(sThumbprint);
        
        // Добавляем обязательный атрибут времени подписания
        oSigningTimeAttr = Новый COMОбъект("CAdESCOM.CPAttribute");
        oSigningTimeAttr.Name = CAPICOM_AUTHENTICATED_ATTRIBUTE_SIGNING_TIME;
        oSigningTimeAttr.Value = ТекущаяДата();
        oSigner.AuthenticatedAttributes2.Add(oSigningTimeAttr);
        
        oSignedData = Новый COMОбъект("CAdESCOM.CadesSignedData");
        oSignedData.ContentEncoding = CADESCOM_BASE64_TO_BINARY;
        oSignedData.Content = СокрЛП(ТекстДляПодписи);
        
        // Формируем подпись
        // EncodingType = 0 означает, что результат (подпись) будет в Base64
        EncodingType = 0;
        sSignedMessage = oSignedData.SignCades(oSigner, CADESCOM_CADES_TYPE, bDetached, EncodingType);
        
        Возврат УбратьСимволы1013(sSignedMessage);
    Исключение
        Сообщить("Ошибка при формировании подписи: " + ОписаниеОшибки());
        Возврат "";
    КонецПопытки;
КонецФункции

Сборка и отправка запроса в API Честного Знака

Рассмотрим завершающий этап: формирование тела запроса и отправку его через HTTPСоединение. Мы будем использовать структуру запроса для создания документа отгрузки (Shipment).


Процедура ОтправитьДокументВЧестныйЗнак(JSONДокумента, ОтпечатокСертификата, ТокенАвторизации)
    
    // 1. Кодируем сам документ в Base64 и очищаем от переносов
    СтрокаДокументаBase64 = УбратьСимволы1013(Base64Строка(ПолучитьДвоичныеДанныеИзСтроки(JSONДокумента, "UTF-8", Ложь)));
    
    // 2. Подписываем полученную Base64-строку
    ПодписьBase64 = ПодписатьТекст(СтрокаДокументаBase64, ОтпечатокСертификата, Истина);
    
    // 3. Формируем структуру пакета для API
    Пакет = Новый Структура;
    Пакет.Вставить("product_document", СтрокаДокументаBase64);
    Пакет.Вставить("document_format", "MANUAL"); // Или XML, в зависимости от задачи
    Пакет.Вставить("type", "LP_SHIP_GOODS");
    Пакет.Вставить("signature", ПодписьBase64);
    
    // Преобразуем пакет в JSON для тела запроса
    ЗаписьJSON = Новый ЗаписьJSON;
    ЗаписьJSON.УстановитьСтроку();
    ЗаписатьJSON(ЗаписьJSON, Пакет);
    ТелоЗапроса = ЗаписьJSON.Закрыть();
    
    // 4. Настройка соединения
    Сервер = "ismp.crpt.ru"; // Адрес может меняться для разных товарных групп
    АдресРесурса = "/api/v3/lk/documents/shipment/create";
    
    Соединение = Новый HTTPСоединение(Сервер, 443, , , , , Новый ЗащищенноеСоединениеOpenSSL());
    
    Запрос = Новый HTTPЗапрос(АдресРесурса);
    Запрос.Заголовки.Вставить("Content-Type", "application/json");
    Запрос.Заголовки.Вставить("Authorization", "Bearer " + ТокенАвторизации);
    Запрос.УстановитьТелоИзСтроки(ТелоЗапроса);
    
    Попытка
        Ответ = Соединение.ОтправитьДляОбработки(Запрос);
        Если Ответ.КодСостояния = 200 Тогда
            Сообщить("Документ успешно отправлен. Идентификатор: " + Ответ.ПолучитьТелоКакСтроку());
        Иначе
            Сообщить("Ошибка сервера: " + Ответ.КодСостояния);
            Сообщить(Ответ.ПолучитьТелоКакСтроку());
        КонецЕсли;
    Исключение
        Сообщить("Ошибка связи: " + ОписаниеОшибки());
    КонецПопытки;
КонецПроцедуры

Дополнительные рекомендации

Если после внедрения кода ошибка сохраняется, проанализируем следующие моменты:

Таким образом, успех интеграции зависит от строгого соблюдения последовательности: формирование компактного JSON — кодирование в Base64 — очистка строки от переносов — создание открепленной подписи CAdES-BES с атрибутом времени — упаковка в финальный JSON-контейнер. Посмотрев на приведенные примеры кода, вы сможете адаптировать их под свою конфигурацию 1С и успешно решить проблему с проверкой подписи. Для решения похожих задач можно использовать обработку для подписи файлов сертификатом ЭЦП средствами 1С.

← На главную