RSA-шифрование в 1С без внешних компонент: чтобы код работал и на Linux, и в тонком клиенте
Иван Недомолков · Опубликовано: · Обновлено:
Задача из платёжной интеграции, знакомая всем, кто подключал эквайринг или платёжный шлюз к 1С. Шлюз принимает данные карты только зашифрованными: отдаёт свой публичный ключ RSA, вы шифруете им номер карты и шлёте шифртекст, а расшифровывает его уже сам шлюз своим приватным ключом. Схема правильная, сырой номер карты нигде в открытом виде не летает. Вопрос один: как в 1С выполнить это шифрование публичным ключом.
Ответ, который вы найдёте по площадкам, почти всегда одинаковый и почти всегда подведёт вас в проде. Разберём, почему, и что с этим делать.
Разбор самой реализации, с длинной арифметикой и делением Кнута на встроенном языке, опубликован у нас на Инфостарте.
Типовое решение через COM и почему оно ломается
Стандартный совет: взять из .NET класс RSACryptoServiceProvider через COM.
КриптоПровайдер = Новый COMОбъект("System.Security.Cryptography.RSACryptoServiceProvider");
КриптоПровайдер.FromXmlString(ПубличныйКлючXML);
Шифр = КриптоПровайдер.Encrypt(Данные, Ложь);
Оно работает. Ровно до момента, когда вы выносите этот код на другую машину. .NET-классы по умолчанию не зарегистрированы для COM, и на голой системе первый же Новый COMОбъект(...) отвечает:
0x80040154: Класс не зарегистрирован
Лечится это регистрацией сборки через regasm, то есть административной вознёй на каждой машине, где будет крутиться код. А на Linux-сервере 1С и в тонком клиенте COM нет вообще, и лечить нечего: код просто не заведётся. Для обработки, которую вы отдаёте заказчику или коллегам, это плохая опора, у половины она не запустится. И проблема не в RSA, а в самой ставке на COM: любой код, который зовёт .NET через COM, привязан к Windows с зарегистрированной сборкой, и молча умирает при первом появлении Linux или тонкого клиента.
Альтернатива: RSA целиком на встроенном языке 1С
Отсюда инженерное решение: написать RSA на чистом BSL, без единой внешней компоненты, чтобы один и тот же код шёл где угодно, в толстом клиенте, в тонком и на Linux-сервере одинаково. RSA по сути одна формула: шифрование это c = m^e mod n, расшифровка m = c^d mod n, возведение в степень по модулю, и всё.
Первым пугает размер. n для боевого ключа это число на 600 с лишним десятичных цифр, а отдельного типа “длинное целое”, как BigInteger в .NET, в 1С нет. Когда мы собирали демо, исходили из того, что Число на таких значениях начнёт терять разряды, и написали длинную арифметику сами: число хранится массивом кусочков по основанию миллион, сложение, вычитание, умножение и деление идут в столбик, поверх них возведение в степень по модулю методом square-and-multiply.
Потом мы проверили саму посылку на сервере 1С 8.3.27, и она не подтвердилась. Число в памяти держит такие значения точно. 2 в степени 2048 (617 цифр), умноженное само на себя, даёт все 1 234 цифры произведения, а частное и остаток через % сходятся с эталоном BigInt цифра в цифру. Возведение в степень по 2048-битному модулю с показателем, в котором 1 024 единичных бита, заняло 173 мс. Шифрование с экспонентой 65537 уложилось в 1 мс. Оба результата тоже сверены с BigInt.
Выходит, для RSA в 1С хватает встроенного типа, а своя длинная арифметика годится как учебный разбор. Из этой истории вышло три урока, и они полезны шире самого RSA.
Урок 1: число держит все цифры, а строка режет
На экране это выглядит ровно как потеря разрядов. Строка(X) и Формат(X, "ЧГ=0") для 2 в степени 2048 возвращают только последние 309 цифр: старшие отрезаются молча, без ошибки и без многоточия. Все 617 цифр отдаёт XMLСтрока(X). Обратное преобразование потерь не даёт: и Число(Стр), и XMLЗначение(Тип("Число"), Стр) читают 617-значную строку точно.
Для криптографии это опаснее самой арифметики. Большое число хочется записать в лог, сохранить строкой или сравнить с эталоном как текст, и на любом из этих шагов Строка() или Формат() подменит его хвостом. Результат будет похож на правильный, но неверен, и в логе ни одной ошибки. Правило простое: большие числа переводим в текст только через XMLСтрока, а в тестах сверяем длину строки с ожидаемой.
Урок 2: скорость решает алгоритм, а не микрооптимизация
Первая рабочая версия движка на массивах считала модульное умножение приёмом “сдвиг-и-сложение”, который обходится без деления, только сложения и вычитания. Красиво, но расшифровка 160-битного ключа занимала 25 секунд, потому что у приёма сложность растёт квадратично по числу бит и на каждый бит идёт полноценная операция над большим числом.
Замена на честное умножение плюс деление с остатком (классический алгоритм Кнута) дала ускорение примерно в 8 раз: то же самое стало считаться за пару секунд. Разница не в том, что мы “подкрутили” старый способ, а в том, что сменили алгоритм: теперь на шаг возведения в степень приходится одно умножение и одно деление вместо сотен сложений. Урок общий: когда что-то работает на порядок медленнее ожидаемого, ищите не микрооптимизацию, а неверный алгоритм в основе.
Второй раз тот же урок сработал сильнее первого. Самый аккуратный столбик на встроенном языке проигрывает умножению, которое платформа выполняет у себя внутри: движок на массивах расшифровывает учебный ключ в 256 бит около 3 секунд, а на встроенном Число возведение в степень по модулю 2048 бит занимает 173 мс.
Урок 3: свою криптографию нельзя проверять “по кругу”
Это самое важное, и мимо этого проходят чаще всего. Соблазн проверить свою реализацию так: зашифровали, расшифровали обратно, получили исходное, значит работает. Это ловушка. Реализация может совпадать по кругу сама с собой и при этом быть неверной относительно чужих реализаций, а платёжному шлюзу нужен именно совместимый шифртекст, тот же самый, что даёт стандартная библиотека. “Расшифровалось обратно” этого не гарантирует.
Поэтому проверка должна быть тройной:
- Классический учебный вектор. Самый маленький осмысленный RSA (
n=3233, e=17, d=413): число 65 обязано зашифроваться в 2790 и расшифроваться обратно в 65. Это проверяет саму формулу на эталонных значениях из учебника. - Внешний эталон, байт в байт. Тот же шифртекст для того же ключа независимо считается стандартной арифметикой больших чисел, и результат сверяется символ в символ в base64, по всем входам: и по номеру карты, и по кириллице, и по адресу почты. Вот это настоящее доказательство совместимости: наш RSA даёт ровно тот результат, что и стандартная библиотека, знак в знак, значит шлюз его поймёт.
- Круговой прогон. И уже поверх этого обычная сверка “зашифровали публичным, расшифровали приватным, получили исходное” на разных строках.
Без пункта 2 первые два ничего не стоят для боевой интеграции. Если делаете криптографию сами, сверка с внешним эталоном обязательна, иначе вы рискуете отправить шлюзу шифртекст, который он не расшифрует, и узнать об этом уже в бою.
Где нативному RSA место, а где нет
Честно про границы, потому что вопрос про прод возникает сразу. Демо учебное: короткий ключ ради скорости и без PKCS#1-паддинга, это показ принципа, а не боевая криптобиблиотека. Его движок на массивах сверен с эталоном, но медленный, и под боевую длину ключа арифметику разумнее отдать встроенному Число. Сверка с внешним эталоном при этом нужна та же самая.
И тут вся суть реальной интеграции: расшифровку делает шлюз, а вам нужно только шифрование публичным ключом. С типовой экспонентой 65537 оно на встроенном Число и ключе 2048 бит заняло в нашем замере 1 мс. Для практической задачи “зашифровать номер карты перед отправкой” нативный BSL-подход рабочий: миллисекунда на шифрование, и код идёт везде, где есть платформа, без регистрации сборок на каждой машине.
Отдельно про подпись запроса, которую шлюзы требуют вместе с шифрованием: она в 1С нативна изначально. SHA256 даёт платформенный объект ХешированиеДанных, base64 встроенные функции, никакого COM. Одна тонкость: ХешированиеДанных доступен на сервере, а не на клиенте, поэтому подпись считается серверным вызовом, зато работает и на Linux тоже.
Посмотреть, как это устроено
Весь цикл (шифрование карты публичным ключом, расшифровка приватным как это сделал бы шлюз, сборка и подпись тела запроса) мы собрали в маленькую обработку на одной кнопке, целиком на встроенном языке, без COM и внешних компонент. Её можно скачать и покрутить на Инфостарте: работает на любой конфигурации от 8.3.14, ставить ничего не надо, код открыт. Полезна, чтобы вживую увидеть, как RSA выглядит без “чёрного ящика” внешней компоненты.
Тот же принцип “криптография и обмен на чистом BSL, без привязки к платформе машины” мы применяем в реальных интеграциях. Если вам нужно подключить платёжный шлюз, эквайринг или другой внешний сервис к 1С так, чтобы решение работало и на Windows, и на Linux-сервере, посмотрите нашу услугу обмена данными 1С: мы делаем интеграции переносимыми и проверяем совместимость с эталоном, а не “у нас на стенде заработало”.