Как быстро проверить доступность закрытого ключа и токена в 1С перед отправкой платежей

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

При реализации автоматической отправки платежных поручений через сервис DirectBank разработчики часто сталкиваются с задачей предварительной валидации: вставлен ли физический токен с электронной подписью в компьютер и доступен ли закрытый ключ. Попытка проверить доступность закрытого ключа путем тестового подписания фрагмента данных для каждого документа в цикле нередко приводит к существенным задержкам. Время выполнения процедуры может варьироваться от нескольких секунд до нескольких минут, что неприемлемо для регламентных и фоновых обработчиков.

Разберем детально, почему штатная проверка работает медленно, как оптимизировать алгоритм встроенного языка 1С и какие альтернативные способы проверки контейнеров существуют.

Почему тестовое подписание каждого документа работает медленно

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


Для Каждого ДокументПлатежки Из СписокДокументов Цикл
    Если ПроверкаСертификатаНаДоступность(ДокументПлатежки) Тогда
        СписокДокументовНаОтправку.Добавить(ДокументПлатежки);
    КонецЕсли;
КонецЦикла;

В функции проверки на каждой итерации создается новый объект МенеджерКриптографии и вызывается метод Подписать(). Выделим основные узкие места такого подхода:

  1. Многократная инициализация криптопровайдера. Создание экземпляра МенеджерКриптографии внутри итератора заставляет операционную систему заново инициализировать библиотеки криптопровайдера КриптоПро CSP и открывать системные дескрипторы для каждого платежного поручения.
  2. Сетевые задержки при проверке цепочки сертификатов и списков отзыва (CRL/OCSP). КриптоПро CSP при каждой операции формирования электронной подписи по умолчанию проверяет статус отзыва сертификата. Если срок действия локального кэша CRL истек, криптопровайдер обращается по сети к серверам удостоверяющего центра. При задержках в канале или блокировках сетевых запросов операция подвисает до истечения системного тайм-аута. Этим объясняется нестабильность: пока кэш в операционной системе актуален — проверка отрабатывает мгновенно, когда он протухает — возникают длинные паузы.
  3. Опрос лишних считывателей в CSP. Если в панели управления КриптоПро CSP настроены неиспользуемые считыватели (например, дисководы, неактивные порты смарт-карт или съемные диски), криптопровайдер последовательно опрашивает каждый интерфейс в поисках нужного контейнера закрытого ключа, что кратно увеличивает время ожидания.
  4. Аппаратные ограничения токена. Физические защищенные носители (токены) работают по интерфейсу CCID/PC/SC с низкой пропускной способностью шины. Каждая криптографическая операция требует времени на инициализацию сессии и вычисление хеша аппаратно-программным модулем токена.

Способ 1. Оптимизация алгоритма 1С и кэширование проверки

В платформе 1С объект СертификатКриптографии содержит исключительно данные открытого ключа. Прямого платформенного метода вроде «ПроверитьНаличиеКонтейнера()» не существует, поэтому факт наличия закрытого ключа подтверждается только через попытку криптографической операции. Однако проверять один и тот же сертификат на каждую платежку избыточно.

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


&НаКлиенте
Процедура АвтоПодписаниеБанка() Экспорт
    ОтключитьОбработчикОжидания("АвтоПодписаниеБанка");
    
    Попытка
        СписокДокументов = ДО_СерверДиректБанк.ПолучитьСписокДокументовДляОтправкиВБанкПоДиректБанку();
        Если СписокДокументов.Количество() = 0 Тогда
            Возврат;
        КонецЕсли;

        // Инициализируем менеджер криптографии один раз за пределами циклов
        Менеджер = Новый МенеджерКриптографии("", "", 80);
        
        // Соответствие: Ключ - СертификатСсылка/Данные, Значение - Булево (Доступен)
        КэшДоступностиСертификатов = Новый Соответствие;
        СписокДокументовНаОтправку = Новый Массив;

        Для Каждого ДокументПлатежки Из СписокДокументов Цикл
            Сертификат = итс_ОбщийМодульСервер.ПолучениеСертификатаИзОбменаСБанком(ДокументПлатежки);
            Если Не ЗначениеЗаполнено(Сертификат) Тогда
                Продолжить;
            КонецЕсли;

            Доступен = КэшДоступностиСертификатов[Сертификат];
            Если Доступен = Неопределено Тогда
                ДанныеСертификата = итс_ОбщийМодульСервер.ПолучениеДанныхСертификатаИзОбменаСБанком(ДокументПлатежки);
                КриптоСертификат = Новый СертификатКриптографии(ДанныеСертификата);
                
                ПарольКлюча = ПроверкаСертификатаНаПарольНаСервере(Сертификат);
                Менеджер.ПарольДоступаКЗакрытомуКлючу = ПарольКлюча;
                
                // Проверяем токен один раз для всей группы документов
                Доступен = ПроверитьТокенБыстро(Менеджер, КриптоСертификат);
                КэшДоступностиСертификатов.Вставить(Сертификат, Доступен);
            КонецЕсли;

            Если Доступен = Истина Тогда
                СписокДокументовНаОтправку.Добавить(ДокументПлатежки);
            КонецЕсли;
        КонецЦикла;

        // Пакетная отправка документов вместо поштучной
        Если СписокДокументовНаОтправку.Количество() > 0 Тогда
            итс_ОбщийМодульСервер.СогласоватьДокумент(СписокДокументовНаОтправку);
            ОбменСБанкамиКлиент.СформироватьПодписатьОтправитьЭД(СписокДокументовНаОтправку);
        КонецЕсли;

    Исключение
        итс_ОбщийМодульСервер.ИТСЗаписатьОшибкуВЖурнал(ОписаниеОшибки());
    КонецПопытки;
    
    ПодключитьОбработчикОжидания("АвтоПодписаниеБанка", 30);
КонецПроцедуры

&НаКлиенте
Функция ПроверитьТокенБыстро(Менеджер, КриптоСертификат)
    ТекстТеста = ПолучитьДвоичныеДанныеИзСтроки("1");
    Попытка
        Менеджер.Подписать(ТекстТеста, КриптоСертификат, "");
        Возврат Истина;
    Исключение
        Возврат Ложь;
    КонецПопытки;
КонецФункции

Обратим внимание на вызов процедуры отправки: передача всего массива СписокДокументовНаОтправку в метод ОбменСБанкамиКлиент.СформироватьПодписатьОтправитьЭД() позволяет типовой библиотеке обмена с банками подписать весь пакет за одну сессию обращения к криптопровайдеру без лишних вызовов.

Способ 2. Отказ от предварительной валидации (принцип EAFP)

В большинстве сценариев автоматизации предварительная тестовая подпись является избыточной операцией. Метод ОбменСБанкамиКлиент.СформироватьПодписатьОтправитьЭД() внутри себя уже содержит все необходимые обработчики ошибок криптографии.

Разберем логику прямого подписания:

  1. Передаем документы на отправку сразу без тестового формирования подписи.
  2. Если закрытый ключ недоступен, токен извлечен или неверен пароль, штатный механизм сформирует исключение.
  3. Перехватываем исключение в блоке Попытка ... Исключение, фиксируем статус в журнале регистрации и повторяем попытку по расписанию через обработчик ожидания.

Такой подход сокращает количество криптографических операций ровно в два раза.

Способ 3. Низкоуровневая проверка наличия контейнера через утилиту csptest

Если требуется строго проверить фактическое физическое присутствие контейнера в системе без выполнения операции вычисления электронной подписи и без обращения к сетевым спискам отзыва, можно использовать консольную утилиту csptest.exe, входящую в дистрибутив КриптоПро CSP.

Посмотрим на пример логики опроса считывателей через командную строку Windows:


Функция ПроверитьНаличиеКонтейнераЧерезCsptest(ИмяКонтейнера) Экспорт
    ПутьУтилиты = "C:\Program Files\Crypto Pro\CSP\csptest.exe";
    КаталогВременный = КаталогВременныхФайлов();
    ФайлРезультата = КаталогВременный + "csptest_out.txt";
    
    Команда = СтрШаблон("""%1"" -keyset -enum_cont -verifycontext -fqcn > ""%2""", ПутьУтилиты, ФайлРезультата);
    
    ЗапуститьПриложение(Команда, , Истина);
    
    ФайлОтвета = Новый Файл(ФайлРезультата);
    Если Не ФайлОтвета.Существует() Тогда
        Возврат Ложь;
    КонецЕсли;
    
    Чтение = Новый ЧтениеТекста(ФайлРезультата, КодировкаТекста.OEM);
    ТекстВывода = Чтение.Прочитать();
    Чтение.Закрыть();
    
    УдалитьФайлы(ФайлРезультата);
    
    // Проверяем вхождение имени контейнера в вывод списка считывателей
    Возврат (Найти(ТекстВывода, ИмяКонтейнера) > 0);
КонецФункции

Команда csptest.exe -keyset -enum_cont -verifycontext -fqcn быстро перечисляет полностью квалифицированные имена всех найденных контейнеров во всех подключенных считывателях без вычисления хешей и сетевых запросов. Если имя целевого контейнера найдено в списке — носитель физически подключен к рабочей станции.

Системные настройки для ускорения работы КриптоПро CSP

Чтобы исключить сетевые зависания при операциях проверки подписи, выполним несколько сервисных настроек операционной системы:

  1. Удаление лишних считывателей. Откроем «Панель управления КриптоПро CSP», перейдем на вкладку «Оборудование» и нажмем «Настроить считыватели». Удалим все неиспользуемые интерфейсы («Все съемные диски», дисководы, неактивные слоты), оставив только конкретный считыватель подключенного токена или реестр.
  2. Локальная установка списков отзыва (CRL). Скачаем актуальный файл списка отзыва УЦ вручную и установим его в системное хранилище через контекстное меню («Установить список отзыва (CRL)»). Это избавит КриптоПро от необходимости выполнять HTTP-запросы при каждой операции подписи.
  3. Проверка тайм-аутов сети. Убедимся, что рабочая станция или сервер имеют стабильный доступ к адресам списков отзыва, указанным в свойствах сертификата (точка дистрибуции CRL), либо настроим политику проверки сертификатов без ожидания сетевого ответа.
← На главную