При интеграции учетных систем (в частности, на платформах 1С 7.7 и 8.х) с API государственной информационной системы маркировки товаров (ГИС МТ "Честный ЗНАК") разработчики часто сталкиваются с проблемами на этапе криптографии. Наиболее распространенные ошибки, которые возвращает сервер: "Подпись не соответствует формату CMS" и "Ошибка: Проверка подписи не пройдена". В этой статье мы подробно разберем причины возникновения этих ошибок, различия в форматах подписей (MIIW... против MIAG...) и рассмотрим надежные методы их устранения, включая использование библиотек CAdESCOM и утилиты csptest.
В процессе обсуждения мы заметили, что разные инструменты генерируют Base64-строки подписи, начинающиеся с разных символов:
Важно понимать: для сервера "Честного ЗНАКа" оба формата являются валидными, если сама структура CMS (PKCS#7) корректна. Ошибка "Подпись не соответствует формату CMS" чаще всего возникает не из-за заголовка, а из-за неправильной структуры данных или проблем с кодировкой. Если вам необходимо дополнительно проверить статус марок, можно использовать специальный запрос информации кодов Честного знака, который поможет убедиться в корректности данных перед подписанием.
Для большинства методов API Честного ЗНАКа (например, ввод в оборот или выгрузка файлов XML для ЭДО с поддержкой маркировки) требуется открепленная подпись (Detached Signature). Это означает, что сама подпись передается отдельно от подписываемых данных (обычно в заголовке или отдельном поле JSON).
Если вы используете COM-объект CAdESCOM.CadesSignedData, необходимо убедиться, что вы вызываете метод подписания с правильными параметрами. Рассмотрим пример функции на языке JScript (который удобно встраивать в 1С через MSScriptControl), позволяющей формировать корректную открепленную подпись.
function SignFile(FileName, Cert, OutFileName) {
// Чтение исходного файла в бинарном режиме
InStream = new ActiveXObject("ADODB.Stream");
InStream.Type = 1; // binary data
InStream.Mode = 3; // read/write
InStream.Open();
InStream.LoadFromFile(FileName);
InData = InStream.Read(-1);
// Инициализация подписанта
Signer = new ActiveXObject("CAdESCOM.CpSigner");
Signer.Certificate = Cert;
Signer.Options = 2; // CAPICOM_CERTIFICATE_INCLUDE_END_ENTITY_ONLY
// Создание объекта подписи
SignedData = new ActiveXObject("CAdESCOM.CadesSignedData");
SignedData.Content = InData;
// Важный момент: Параметры метода SignCades
// 1 (bDetached) = true (открепленная подпись)
// 0 (EncodingType) = CADESCOM_BASE64_TO_BINARY (вывод в Base64)
// В примере ниже используется SignCades(Signer, 1, 1, 0)
// Второй параметр: CadesType (1 = CADES_BES)
// Третий параметр: bDetached (1 = True, открепленная)
// Четвертый параметр: EncodingType (0 = Base64)
OutSignedData = SignedData.SignCades(Signer, 1, 1, 0);
// Сохранение подписи в файл
OutStream = new ActiveXObject("ADODB.Stream");
OutStream.Type = 2; // text data
OutStream.CharSet = "utf-8";
OutStream.Mode = 3; // read/write
OutStream.Open();
OutStream.WriteText(OutSignedData);
OutStream.SaveToFile(OutFileName, 2);
OutStream.Close();
return(0);
}
Обратите внимание на использование объекта ADODB.Stream. Для чтения исходного файла мы используем Type = 1 (бинарный), чтобы избежать искажения байтов при чтении. Это критически важно, так как подпись рассчитывается от бинарного представления данных.
Даже если формат CMS верен, сервер может вернуть ошибку "Проверка подписи не пройдена" (при этом HTTP статус может быть 200). Это означает, что криптографический хэш данных, который вы подписали, не совпадает с хэшем данных, которые получил сервер.
Основная причина: различие в кодировках.
1С хранит строки в формате UCS-2 (UTF-16LE). API Честного ЗНАКа ожидает UTF-8. Если вы сохраняете JSON из 1С в файл как текст, система может добавить в начало файла метку порядка байт (BOM — Byte Order Mark). Для UTF-8 это три байта: EF BB BF.
Если вы подпишете файл с BOM, а на сервер отправите тело запроса без BOM (или наоборот), хэши не совпадут. Поэтому, прежде чем подписывать данные, мы должны убедиться, что работаем с "чистым" бинарным массивом данных в UTF-8 без лишних заголовков.
Если манипуляции с COM-объектами и потоками ADODB вызывают сложности или нестабильный результат (особенно в старых версиях платформы, таких как 1С 7.7, где реализуется разрешительный режим на кассах), мы рекомендуем использовать штатную утилиту csptest.exe, входящую в состав дистрибутива КриптоПро CSP.
Этот метод работает надежнее, так как утилита работает с файлами на низком уровне, исключая проблемы конвертации строк внутри 1С. Также это отличное решение, если вы хотите самостоятельно управлять процессом подписания.
Алгоритм действий:
ADODB.Stream для конвертации строки в UTF-8.csptest.exe через ЗапуститьПриложение или КомандаСистемы.Пример командной строки для формирования открепленной подписи в формате Base64:
"C:\Program Files\Crypto Pro\CSP\csptest.exe" -sfsign -sign -detached -base64 -in "путь_к_файлу_данных.json" -out "путь_к_файлу_подписи.txt" -my "ИмяСертификатаИлиОтпечаток"
Разберем ключи команды:
-sfsign — режим создания упрощенной подписи.-sign — команда на подписание.-detached — важно! создание открепленной подписи.-base64 — результат будет сохранен в кодировке Base64 (а не в бинарном DER).-in и -out — пути к входному и выходному файлам.-my — критерий поиска сертификата в личном хранилище (по части имени субъекта).Использование этого метода решило проблему автора темы, когда стандартные компоненты выдавали ошибку проверки.
При получении подписи (через CAdESCOM или файл) она может содержать переносы строк (\r\n), которые иногда мешают корректной передаче в JSON. Рекомендуется удалять их перед отправкой.
Функция очистки подписи от переносов строк и заголовков:
Функция УбратьПереносыСтрок(ПутьКСертификату) Экспорт
ТекстСертификата = "";
// Чтение файла
АдоДБСтрим = СоздатьОбъект("ADODB.Stream");
АдоДБСтрим.Mode = 3;
АдоДБСтрим.Type = 2; // текст
АдоДБСтрим.charset = "utf-8";
АдоДБСтрим.Open();
АдоДБСтрим.LoadFromFile(ПутьКСертификату);
ТекстСертификата = АдоДБСтрим.ReadText(-1);
АдоДБСтрим.Close();
ВозврPublic_cert = "";
Для СчСтрок = 1 По СтрКоличествоСтрок(ТекстСертификата) Цикл
ТекСтрока = СтрПолучитьСтроку(ТекстСертификата, СчСтрок);
// Пропускаем заголовки BEGIN/END, если они есть
Если Лев(ТекСтрока, 4) = "----" Тогда
Продолжить;
КонецЕсли;
// Удаляем переносы
ТекСтрока = СтрЗаменить(ТекСтрока, РазделительСтрок, "");
ВозврPublic_cert = ВозврPublic_cert + ТекСтрока;
КонецЦикла;
Возврат ВозврPublic_cert;
КонецФункции
Также, если вам необходимо кодировать данные в Base64 штатными средствами Windows (без внешних компонент), можно использовать объект CDO.Message. Это особенно актуально для старых платформ.
Функция КодироватьBase64(Стр, Кодировка = "base64") Экспорт
ЦДО = СоздатьОбъект("CDO.Message");
БодиПарт = ЦДО.BodyPart;
Поля = БодиПарт.Fields;
Поле = Поля.Item("urn:schemas:mailheader:content-type");
Поле.Value = "text/plain; charset=""windows-1251""";
Поля.Update();
// Запись исходного текста
Стрим = БодиПарт.GetDecodedContentStream();
Стрим.charset = "windows-1251";
Стрим.WriteText(Стр);
Стрим.Flush();
// Получение закодированного текста
БодиПарт.ContentTransferEncoding = Кодировка;
Стрим = БодиПарт.GetEncodedContentStream();
// CDO может добавлять переносы строк, их лучше удалить
Результат = Стрим.ReadText();
// Здесь рекомендуется добавить очистку от символов перевода строки, если API этого требует
Возврат Результат;
КонецФункции
Для успешной работы с API Честного ЗНАКа обратите внимание на следующие пункты:
csptest.exe с параметрами -detached -base64. Это наиболее стабильное решение.