При интеграции 1С с современными банковскими или другими внешними сервисами, использующими протокол OAuth 2.0 с расширением PKCE (Proof Key for Code Exchange), мы часто сталкиваемся с необходимостью генерации специального параметра code_challenge. Этот параметр играет ключевую роль в обеспечении безопасности процесса авторизации, особенно для публичных клиентов, таких как 1С-приложения, которые не могут надежно хранить секретный ключ клиента.
Основная формула для генерации code_challenge выглядит следующим образом: BASE64URL-ENCODE(SHA256(ASCII(code_verifier))) = code_challenge. На первый взгляд она кажется простой, но на практике при реализации в 1С могут возникнуть сложности, связанные с нюансами хеширования и кодирования.
Давайте подробно рассмотрим, как правильно реализовать этот механизм в 1С 8.3, используя примеры из сообщений на форуме, а также общие рекомендации.
Прежде чем мы перейдем к коду, разберем каждый компонент формулы BASE64URL-ENCODE(SHA256(ASCII(code_verifier))), чтобы точно понять, что от нас требуется:
code_verifier: Это случайная строка высокой энтропии, которую мы генерируем в клиентском приложении (в нашем случае в 1С). Стандарт RFC 7636 предписывает, что она должна состоять из символов [A-Z], [a-z], [0-9], а также дефиса (-), точки (.), знака подчеркивания (_) и тильды (~). Длина строки должна быть от 43 до 128 символов. Мы должны сгенерировать эту строку самостоятельно и надежно сохранить ее до этапа получения токена.ASCII(code_verifier): Здесь указывается, что строка code_verifier должна быть интерпретирована как ASCII-последовательность байтов перед хешированием. Платформа 1С по умолчанию работает со строками в кодировке UTF-8. Однако, поскольку разрешенный набор символов для code_verifier полностью совпадает между ASCII и UTF-8, мы можем просто передавать строку как есть в функции хеширования 1С, и результат будет корректным.SHA256: Это стандартный алгоритм криптографического хеширования, который преобразует входные данные (байты code_verifier) в фиксированную 256-битную (32-байтовую) хеш-сумму. Ключевой момент, который вызывает наибольшие трудности в 1С, заключается в том, как именно получить эту 32-байтовую хеш-сумму в виде двоичных данных, а не ее строкового (шестнадцатеричного) представления. Объект ХешированиеДанных в 1С имеет свойство ХешСумма, которое возвращает хеш в виде шестнадцатеричной строки (например, "E9Melhoa..."), а не в виде необработанных двоичных данных.BASE64URL-ENCODE: Это вариант кодирования Base64, адаптированный для использования в URL-адресах и файловых системах. Отличия от стандартного Base64 заключаются в следующем:
+ заменяются на - (дефис)./ заменяются на _ (знак подчеркивания).= (padding characters) в конце строки удаляются.Давайте посмотрим, как мы можем применить эти знания на практике в 1С.
code_challenge в 1С (подход, показанный в сообщении 4)Предположим, у нас есть тестовый code_verifier, который банк предоставил для проверки:
code_verifier: dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
И ожидаемый code_challenge:
code_challenge: E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
Мы видим, что в сообщении 4 на форуме был предложен рабочий вариант кода, который позволил получить искомый code_challenge. Давайте разберем его по шагам:
ВходнаяСтрока = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk";
Хэш = Новый ХешированиеДанных(ХешФункция.SHA256);
Хэш.Добавить(ВходнаяСтрока);
СтрокаBase64 = Base64Строка(Хэш.ХешСумма);
СтрокаBase64 = СтрЗаменить(СтрокаBase64, "=", "");
СтрокаBase64 = СтрЗаменить(СтрокаBase64, "+", "-");
СтрокаBase64 = СтрЗаменить(СтрокаBase64, "/", "_");
ВыходнаяСтрока = СтрокаBase64;
Давайте проанализируем каждую строку этого кода:
ВходнаяСтрока = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk";ВходнаяСтрока нашим code_verifier. Это исходная строка, которую мы будем хешировать.Хэш = Новый ХешированиеДанных(ХешФункция.SHA256);ХешированиеДанных, указывая в качестве алгоритма хеширования SHA256. Этот объект является стандартным средством 1С для работы с криптографическими хешами.Хэш.Добавить(ВходнаяСтрока);code_verifier в объект хеширования. Платформа 1С самостоятельно преобразует эту строку в последовательность байтов (в UTF-8) и выполнит над ними SHA256 хеширование. Для символов, разрешенных в code_verifier, кодировка UTF-8 будет эквивалентна ASCII.СтрокаBase64 = Base64Строка(Хэш.ХешСумма);Хэш.ХешСумма возвращает шестнадцатеричное строковое представление 32-байтового хеша (например, "E9AFB4B...F1"). Метод Base64Строка() в 1С может принимать либо ДвоичныеДанные, либо просто Строку. Если передать ему строку, он закодирует в Base64 байтовое представление этой строки. В данном случае, мы кодируем в Base64 не исходные 32 байта хеша, а байты, представляющие символы шестнадцатеричной строки хеша (например, байт для символа 'E', байт для символа '9' и так далее). Как показал опыт авторов сообщения, именно такой подход оказался рабочим для конкретного банка. Это означает, что банк, скорее всего, ожидает, что мы сначала получим хеш в виде шестнадцатеричной строки, а затем закодируем эту шестнадцатеричную строку в Base64.СтрокаBase64 = СтрЗаменить(СтрокаBase64, "=", "");= (padding characters) из конца строки.СтрокаBase64 = СтрЗаменить(СтрокаBase64, "+", "-");+ на - (дефисы).СтрокаBase64 = СтрЗаменить(СтрокаBase64, "/", "_");/ на _ (знаки подчеркивания).ВыходнаяСтрока = СтрокаBase64;ВыходнаяСтрока и будет нашим итоговым code_challenge.При использовании данного кода с code_verifier dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk мы должны получить code_challenge E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM, что подтверждает корректность данного подхода для тестовых значений.
Как мы уже упоминали, требования к генерации code_challenge могут отличаться. В сообщении 9 приведено критически важное уточнение, характерное для многих банковских и платежных систем:
Полученное значение хеша кодируется алгоритмом Base64 (кодируется не его битовое представление, а его шестнадцатеричное строковое представление в нижнем регистре)
Это прямо подтверждает интерпретацию, которую мы видели в сообщении 4 – что кодироваться должна именно строка хеша. Однако здесь добавляется еще одно условие: шестнадцатеричное представление должно быть в нижнем регистре.
Рассмотрим пример кода, демонстрирующий этот подход:
// Пусть КодШифра - это наш code_verifier
КодШифра = "test_code_verifier_12345"; // Пример code_verifier
Хеш = Новый ХешированиеДанных(ХешФункция.SHA256);
Хеш.Добавить(КодШифра);
// Получаем шестнадцатеричную строку хеша, приводим к нижнему регистру и удаляем пробелы
// Если ХешСумма содержит пробелы (что обычно не так для SHA256), их нужно удалить.
// Использование Нрег() для приведения к нижнему регистру.
sha256_string = СтрЗаменить(Нрег(Хеш.ХешСумма), " ", "");
// Теперь эту шестнадцатеричную строку нужно преобразовать в двоичные данные,
// чтобы Base64Строка() правильно ее закодировала.
// Предполагается наличие вспомогательной функции ПолучитьДвоичныеДанныеИзСтроки,
// которая конвертирует строку (например, "abcdef") в байты, представляющие эти символы.
// Например, для строки "ab" это будут байты [0x61, 0x62].
Попытка
ДвоичныеДанныеИзХешСтроки = ПолучитьДвоичныеДанныеИзСтроки(sha256_string);
Исключение
// В случае отсутствия такой функции, можно использовать более универсальный,
// но менее явный способ, если Base64Строка() умеет кодировать строковое представление напрямую
// как будто это "ДвоичныеДанные", что часто работает для ASCII-подобных строк.
// Однако, для строгих требований к кодировке, лучше иметь явное преобразование.
ДвоичныеДанныеИзХешСтроки = Новый ДвоичныеДанные(КодировкиТекста.UTF8, sha256_string);
КонецПопытки;
// Кодируем полученные двоичные данные в Base64.
// Затем применяем Base64URL-специфичные замены.
signatureBase64 = Base64Строка(ДвоичныеДанныеИзХешСтроки);
signatureBase64URL = СтрЗаменить(signatureBase64, "=", "");
signatureBase64URL = СтрЗаменить(signatureBase64URL, "+", "-");
signatureBase64URL = СтрЗаменить(signatureBase64URL, "/", "_");
code_challenge = signatureBase64URL;
Разберем ключевые отличия этого подхода:
sha256_string = СтрЗаменить(Нрег(Хеш.ХешСумма), " ", "");Нрег(), что может быть требованием конкретного банка. Также убираются любые пробелы, если они вдруг присутствуют в строке (что маловероятно для ХешСумма, но является хорошей практикой).ДвоичныеДанныеИзХешСтроки = ПолучитьДвоичныеДанныеИзСтроки(sha256_string);ПолучитьДвоичныеДанныеИзСтроки. Если такой функции нет, то в 1С мы можем создать ДвоичныеДанные из строки, которая будет содержать байты, соответствующие символам строки. Например, для строки "a1b2", это будут байты, представляющие символы 'a', '1', 'b', '2'. Универсальный способ создания таких данных из строки: Новый ДвоичныеДанные(КодировкиТекста.UTF8, sha256_string). Это преобразует символы шестнадцатеричной строки в их байтовое представление в UTF-8, что затем кодируется в Base64.signatureBase64 = Base64Строка(ДвоичныеДанныеИзХешСтроки);code_challenge.Выбор между этими двумя подходами (кодировать в Base64 напрямую Хеш.ХешСумма или предварительно приводить ее к нижнему регистру и/или использовать явное преобразование в ДвоичныеДанные) полностью зависит от точной спецификации API банка. Если первый вариант из сообщения 4 работает для тестовых данных, значит, он соответствует их требованиям.
Чтобы не повторять код для преобразования Base64 в Base64URL каждый раз, мы можем вынести его в отдельную вспомогательную функцию:
// Функция преобразует стандартную Base64 строку в Base64URL формат
Функция ПреобразоватьBase64ВBase64URL(СтрокаBase64) Экспорт
// 1. Удаляем символы-заполнители "="
СтрокаBase64 = СтрЗаменить(СтрокаBase64, "=", "");
// 2. Заменяем "+" на "-"
СтрокаBase64 = СтрЗаменить(СтрокаBase64, "+", "-");
// 3. Заменяем "/" на "_"
СтрокаBase64 = СтрЗаменить(СтрокаBase64, "/", "_");
Возврат СтрокаBase64;
КонецФункции
Тогда наш итоговый код для генерации code_challenge (используя подход из сообщения 4) будет выглядеть более компактно:
code_verifier = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk";
Хэш = Новый ХешированиеДанных(ХешФункция.SHA256);
Хэш.Добавить(code_verifier);
// Получаем Base64 от шестнадцатеричной строки хеша
СтандартнаяСтрокаBase64 = Base64Строка(Хэш.ХешСумма);
// Преобразуем в Base64URL формат
code_challenge = ПреобразоватьBase64ВBase64URL(СтандартнаяСтрокаBase64);
Сообщить("Сгенерированный code_challenge: " + code_challenge);
// Ожидаем: E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
После успешной генерации code_challenge и получения кода авторизации (на который обычно перенаправляется пользователь), следующим шагом является обмен этого кода на токен доступа. В этот момент многие пользователи сталкиваются с ошибкой 500 Internal Server Error от сервера банка, как это описано в сообщении 6:
{"error": {"code": "serverError","message": "Internal Server Error."}}
Хотя это общая серверная ошибка, в контексте OAuth и PKCE она почти всегда указывает на проблему в запросе клиента. Сервер возвращает такую ошибку, чтобы не раскрывать чувствительную информацию о конкретной причине сбоя. Мы проанализируем наиболее частые причины и способы их устранения:
code_verifier / Неверный code_challenge:code_challenge, который мы отправили на первом шаге (при запросе кода авторизации), был сгенерирован неверно (например, из-за неправильной интерпретации требований к Base64/Base64URL или специфики SHA256 в 1С), то когда мы отправляем изначальный code_verifier на этапе обмена токенов, сервер не может подтвердить его подлинность. Сервер сам выполняет операцию BASE64URL-ENCODE(SHA256(ASCII(полученного_code_verifier))) и сравнивает результат с тем code_challenge, который мы прислали в самом начале. Если они не совпадают, это приводит к ошибке. Убедитесь, что ваш code_challenge генерируется абсолютно точно в соответствии с документацией банка и тестовыми примерами.redirect_uri:redirect_uri), который мы отправляем в запросе на получение токена, должен **абсолютно точно** совпадать с тем, который был отправлен при запросе кода авторизации. Любое расхождение, включая регистр символов, наличие или отсутствие http:// vs https://, или наличие/отсутствие завершающего слэша (/), может привести к ошибке. Убедитесь, что этот параметр идентичен в обоих запросах и что он зарегистрирован в настройках вашего клиента на стороне банка.client_id:client_id, который вы отправляете в запросе на получение токена, точно соответствует вашему зарегистрированному идентификатору клиента.grant_type:grant_type=authorization_code. Убедитесь, что этот параметр указан корректно.client_id, grant_type, code, redirect_uri, code_verifier), с документацией банка. Малейшая опечатка может вызвать 500 ошибку.Рекомендация: При возникновении 500 ошибки, мы настоятельно рекомендуем тщательно сверять документацию банка и использовать онлайн-генераторы code_challenge (такие, как упоминалось в сообщении 5, хотя ссылки мы здесь не приводим) для проверки каждого шага генерации и убедиться, что ваши промежуточные значения соответствуют ожидаемым. Также полезно использовать инструменты для отладки HTTP-запросов (например, Fiddler, Postman или инструменты разработчика в браузере), чтобы точно видеть, какие данные отправляются на сервер банка.
Генерация code_challenge в 1С для авторизации по протоколу OAuth 2.0 PKCE требует внимательности к деталям, особенно в части обработки хеш-суммы и особенностей Base64URL кодирования. Как мы выяснили, ключевую роль играет точная интерпретация требований банка: кодируется ли в Base64 непосредственно двоичное представление хеша, или же его шестнадцатеричная строка (возможно, в нижнем регистре). Практические примеры, разобранные на основе сообщений с форума, предоставляют рабочие решения для 1С 8.3.
Понимание и правильное применение этих методов, а также внимательный подход к отладке HTTP-запросов, помогут нам успешно интегрировать 1С с современными внешними сервисами и преодолеть типовые сложности, связанные с авторизацией.