Как получить аналог C# XmlDsigC14NTransform() для хеширования XML в 1С?

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

При интеграции 1С с внешними системами, сайтами или государственными порталами часто возникает задача формирования цифровой подписи (ЭЦП) или расчета хеш-суммы (например, SHA1) для передаваемых XML-файлов. При попытке перенести логику обмена из стороннего приложения на C# в 1С разработчики сталкиваются с проблемой: метод хеширования в 1С выдает совершенно другой результат по сравнению с C# кодом, использующим класс XmlDsigC14NTransform. Разберем по шагам, почему это происходит, и рассмотрим несколько надежных способов получить идентичный хеш в среде 1С:Предприятие.

Выясним причину: почему хеши файлов не совпадают?

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

Класс XmlDsigC14NTransform в .NET перед получением хеша выполняет процесс "нормализации" (канонизации) структуры XML в соответствии со стандартами консорциума W3C. Что именно происходит при канонизации:

  1. Атрибуты внутри узлов сортируются в алфавитном порядке.
  2. Кавычки вокруг значений атрибутов унифицируются (обычно приводятся к двойным).
  3. Удаляются или преобразуются лишние переносы строк, табуляции и пробелы между тегами.
  4. Удаляется XML-декларация (например, <?xml version="1.0" encoding="UTF-8"?>).
  5. Удаляются невидимые маркеры кодировки, такие как BOM (Byte Order Mark) из UTF-8.
  6. Пространства имен (namespaces) приводятся к единому формату и правилам наследования.

Именно поэтому, если просто подать текст XML в объект 1С ХешированиеДанных, результат никогда не совпадет с тем, что выдает transform.GetDigestedOutput(new SHA1Managed()) на стороне C#. Чтобы получить нужный результат, нам необходимо сначала канонизировать XML. Рассмотрим три основных решения этой задачи.

Решение 1: Используем штатные средства 1С (Рекомендуется)

Начиная с версий платформы 1С:Предприятие 8.3.9, в системе реализован встроенный объект ПреобразованиеККаноническомуXML, который является прямым и полноценным аналогом класса XmlDsigC14NTransform. Использование встроенных объектов всегда предпочтительнее, так как избавляет от необходимости регистрировать внешние компоненты и решать проблемы совместимости версий ОС.

Проанализируем алгоритм работы в 1С. Нам потребуется прочитать исходный XML, построить из него объектную модель DOM (Document Object Model), выполнить канонизацию и вычислить хеш-сумму.

Посмотрим на пример кода, который реализует эту логику:


// 1. Инициализируем чтение нашего исходного XML из файла или строки
Чтение = Новый ЧтениеXML;
Чтение.ОткрытьФайл("C:\temp\source.xml"); // или УстановитьСтроку(ТекстXML)

// 2. Строим DOM-модель документа
Построитель = Новый ПостроительDOM;
ДокументDOM = Построитель.Прочитать(Чтение);
Чтение.Закрыть();

// 3. Создаем объект для канонизации
Канон = Новый ПреобразованиеККаноническомуXML();

// 4. Получаем нормализованную строку. 
// Важно выбрать правильный тип канонизации из системного перечисления
СтрокаКанон = Канон.КанонизироватьВСтроку(ДокументDOM, ТипКаноническогоXML.ИсключающийКаноническийXML);

// 5. Вычисляем хеш-сумму (например, алгоритм SHA1)
Хеширование = Новый ХешированиеДанных(ХешФункция.SHA1);
Хеширование.Добавить(СтрокаКанон);

// Получаем результат в виде строки
ХешСумма = Хеширование.ХешСумма;
Сообщить(СтрЗаменить(Строка(ХешСумма), " ", "")); // Убираем пробелы, если нужно

Важный нюанс: В методе КанонизироватьВСтроку вторым параметром передается значение перечисления ТипКаноническогоXML. Доступны различные варианты: КаноническийXML, КаноническийXMLСКомментариями, ИсключающийКаноническийXML и т.д. Чтобы результат полностью совпал с C# кодом new XmlDsigC14NTransform(false), необходимо поэкспериментировать с типами канонизации в зависимости от того, какой именно стандарт W3C ожидает принимающая сторона. Обычно используется ИсключающийКаноническийXML.

Решение 2: Создание собственной COM-компоненты на C#

Если штатные средства по каким-то причинам не подходят или вам необходимо перенести сложный комплексный код шифрования целиком, можно создать собственную DLL-библиотеку (COM-сервер) на языке C# и вызывать её из 1С. Рассмотрим подробнее, как это реализовать.

Для начала создадим проект библиотеки классов (Class Library) в Visual Studio. Важно: если сервер приложений 1С работает на устаревших ОС, таких как Windows Server 2008 R2, не используйте современные платформы .NET 5 или .NET Core, так как они там не зарегистрируются. Используйте .NET Framework 4.6 или выше.

В менеджере пакетов NuGet необходимо добавить ссылку на пакет System.Security.Cryptography.Xml. Разберем рабочий код COM-объекта:


using System;
using System.Runtime.InteropServices;
using System.Xml;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;

namespace COMServerDES
{ 
    // Определяем интерфейс
    [ComVisible(true)]
    [Guid("4C23DF7D-0013-0001-1962-7929382A5D32")]
    [InterfaceType(ComInterfaceType.InterfaceIsDual)]
    public interface IServer 
    { 
        string SHA1(string content); 
    }

    // Реализуем класс
    [ComVisible(true)]
    [Guid("5553D96D-0013-0001-1962-E3D8FCE913A1")]
    [ProgId("COMServerDES.Server")]
    public class Server : IServer
    { 
        [ComVisible(true)]
        [DispId(1)]
        public string SHA1(string content)
        { 
            try 
            {
                // Загружаем XML
                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.LoadXml(content);
                
                // Инициализируем трансформер
                XmlDsigC14NTransform transform = new XmlDsigC14NTransform(false);
                transform.LoadInput(xmlDoc);
                
                // Получаем хеш и конвертируем его в строку
                return BitConverter.ToString(transform.GetDigestedOutput(new SHA1Managed())).Replace("-", string.Empty); 
            }
            catch (Exception e) 
            { 
                return "Ошибка: " + e.Message; 
            } 
        } 
    } 
}

После компиляции библиотеку необходимо зарегистрировать в операционной системе с помощью утилиты RegAsm.exe (обязательно с ключом /codebase или предварительно поместив сборку в GAC), запустив командную строку от имени Администратора. Затем в коде 1С вызов будет выглядеть предельно просто:


КомСервер = Новый COMОбъект("COMServerDES.Server");
ХешСтрока = КомСервер.SHA1(ТекстИсходногоXML);
Сообщить(ХешСтрока);

Решение 3: Прямой вызов классов .NET через компоненту-обертку

Существует компромиссный вариант: не писать собственный COM-объект на C#, но использовать оригинальные классы .NET прямо из кода 1С. Для этого можно применить популярную универсальную внешнюю компоненту (например, известную в сообществе 1С как NetObjectToIDispatch45).

Эта компонента выступает в роли "моста" между COM-средой 1С и платформой .NET Framework. Разберем по шагам, как повторить логику C# прямо в конфигураторе 1С с ее помощью:


// 1. Создаем объект-обертку (при условии, что компонента зарегистрирована)
Врап = Новый COMОбъект("NetObjectToIDispatch45");

// 2. Загружаем необходимую сборку, содержащую классы для криптографии XML
Врап.ЗагрузитьСборку("System.Security.Cryptography.Xml");

// 3. Создаем нужные объекты .NET
XmlDoc = Врап.СоздатьОбъект("System.Xml.XmlDocument");
// Загружаем наш XML из строки
XmlDoc.LoadXml(ТекстXML);

// 4. Создаем объект трансформации канонизации
C14N = Врап.СоздатьОбъект("System.Security.Cryptography.Xml.XmlDsigC14NTransform");
// Загружаем документ в трансформер
C14N.LoadInput(XmlDoc);

// 5. Создаем объект вычисления хеша SHA1
SHA1 = Врап.СоздатьОбъект("System.Security.Cryptography.SHA1Managed");

// 6. Получаем массив байт хеша
МассивБайт = C14N.GetDigestedOutput(SHA1);

// 7. Конвертируем массив байт в привычную шестнадцатеричную строку
BitConverter = Врап.СоздатьОбъект("System.BitConverter");
СтрокаСДефисами = BitConverter.ToString(МассивБайт);

// Приводим к нужному виду (убираем дефисы)
ИтоговыйХеш = СтрЗаменить(СтрокаСДефисами, "-", "");
Сообщить(ИтоговыйХеш);

Этот способ дает максимальную гибкость, так как позволяет использовать любые методы и свойства объектов .NET без перекомпиляции промежуточных DLL. Однако он требует установки и обслуживания сторонней компоненты на серверах 1С.

Подводя итог, можно сказать, что для большинства современных конфигураций наиболее правильным, надежным и кроссплатформенным решением является первый вариант — использование встроенного в платформу объекта ПреобразованиеККаноническомуXML в связке с объектом ХешированиеДанных.

← На главную