Лёгкая статья про стандарты HMAC и JWT с небольшой теорией и исходным кодом.
Коллеги, позвольте вам рассказать о вещах, которые далеки от обычной разработки в 1С.
Вероятно, что сейчас у вас нет необходимости использовать HMAC и JWT, но время не стоит на месте, и, возможно, в очередном проекте интеграции вы задействуете описываемые ниже стандарты.
Часть первая, теоретическая.
HMAC (Hash-based message authentication code) — это хэш, который вычисляется, основываясь на двух значениях: ‘ключ’ и ‘сообщение’. Такой хэш нужен, чтобы гарантировать, что данные, передаваемые в ненадежной среде, не были изменены посторонними лицами.
Зачем же нам использовать HMAC, когда у нас есть, например, HTTPS?
HMAC полезен, когда участников в обмене сообщениями больше, чем два.
Например, у нас есть три участника:
- ‘Провайдер API’ — сторонний сервис, который принимает запросы на отправку открыток и букетов
- ‘Сервер’ — серверная часть вашего приложения
- ‘Клиент’ — клиентская часть, с которой работают пользователи
‘Провайдер API’ предоставляет две вещи для выполнения запросов – это AccounID и SecretKey. ‘Провайдер API’ ни чего не знает про ваших пользователей, чтобы принять запрос на отправку открытки, ему нужно удостовериться, что просящий знает AccounID и SecretKey.
‘Сервер’ в свою очередь должен управлять тем, какие пользователи имеют право отправлять букеты, а каким разрешены только открытки.
Разумеется, очевидное решение задачи заключается в том, чтобы все клиентские запросы направить через ‘Сервер’:
- ‘Клиент’ — формирует запрос на отправку открытки для ‘Сервера’;
- ‘Сервер’ — проверяет права конкретного пользователя и если всё хорошо, то перенаправляет вызов на ‘Провайдера API’;
- ‘Провайдер API’ делает необходимые действия;
- Далее по цепочке обратно передается результат вызова.
Но что, если у нас клиенты генерируют десятки тысяч таких запросов, и не хотят долго ждать результат выполнения? Или наши запросы содержат потоковое аудио (для музыкальных открыток), которым не хотелось бы грузить ‘Сервер’? Более того, а что, если ‘Клиент’ передает конфиденциальные сведения, которые и вовсе не должны попасть на ‘Сервер’ (букет с интимным посланием)?
Почему бы нам сразу не слать запросы с ‘Клиента’ на ‘Провайдер API’?
Тогда нам придется ‘Клиенту’ сообщить AccounID и SecretKey, которые нужны ‘Провайдеру API’. Но поскольку у нас разные клиенты имеют разные права (открытки, букеты) и в какой то момент у клиента права могут быть и вовсе отозваны, то мы не можем сообщать ‘Клиенту’ AccounID и SecretKey.
В этот момент нам и пригодится HMAC.
Благодаря HMAC мы можем построить работу следующим образом:
- ‘Клиент’ запрашивает у ‘Сервера’ специальный Token
- ‘Сервер’ делает Token с помощью HMAC, учитывая права пользователя и ограничивая действие токена по времени:
Token = HMAC(SecretKey, AccounID + ПравоПользователя + ДатаВремяДоступа)
Теперь пользователь может обратиться напрямую к ‘Провайдеру API’ за конкретной услугой и предоставить Token, AccounID и ДатаВремяДоступа. ‘Провайдер API’ вычисляет Token и сверяет его с тем, что прислал ‘Клиент’ и той услугой которую ‘Клиент’ хочет получить.
В данной схеме ‘Клиент’ является той самой ненадежной средой. Фактически запрос должен делать ‘Сервер’, потому что ‘Провайдер API’ сказал свой SecretKey только ‘Серверу’. Но наш ‘Сервер’ не хочет делать запросы и разрешает на время ‘Клиенту’ самостоятельно делать запросы. Наш ‘Сервер’ не доверяет ‘Клиенту’ и использует HMAC, чтобы обеспечить неизменность выданных разрешений на использование услуг ‘Провайдера API’.
Часть вторая, считаем HMAC.
Давайте посмотрим под капот и узнаем, что же внутри функции HMAC.
Функция HMAC на вход принимает SecretKey и Message типа ДвоичныеДанные и вид хэш функции.
Function HMAC(Val SecretKey, Val Message, Val HashFunc) Export
BlSz = 64;
// Если ключ больще чем размер блока, то в качестве ключа используем хэш от ключа
If SecretKey.Size() > BlSz Then
SecretKey = Hash(SecretKey, HashFunc);
EndIf;
EmptyBin = GetBinaryDataFromString("");
SecretKey = BinLeft(SecretKey, BlSz);
// Если ключ меньше блока, то добиваем его нулями до размера блока
К0 = BinRightPad(SecretKey, BlSz, "0x00");
// Делаем k_ipad, выполняем операцию «побитовое исключающее ИЛИ» ключа c константой 0x36
ipad = BinRightPad(EmptyBin, BlSz, "0x36");
k_ipad = BinBitwiseXOR(К0, ipad);
// Делаем k_opad, выполняем операцию «побитовое исключающее ИЛИ» ключа c константой 0x5c
opad = BinRightPad(EmptyBin, BlSz, "0x5C");
k_opad = BinBitwiseXOR(К0, opad);
// склеиваем k_ipad и сообщение
k_ipad_Message = BinConcat(k_ipad, Message);
// вычисляем хэши и получаем результат
k_opad_Hash = BinConcat(k_opad, Hash(k_ipad_Message, HashFunc));
res = Hash(k_opad_Hash, HashFunc);
Return res;
EndFunction
Функции BinLeft, BinRightPad, BinBitwiseXOR и BinConcat реализованы с использованием возможностей платформы, которые появились в версии 8.3.10.2168.
Теперь мы можем делать хэш и комбинировать данные для подписи любым способом.
Например, так:
Token = HMAC(SecretKey, AccounID + ПравоПользователя + УникальныйИдентификатор + "допДанные7" + ДатаВремяДоступа + ИмяПользователя)
Такая свобода действий может породить хаос и как следствие – увеличение сроков разработки и интеграции. Что, если бы у нас был стандарт, по которому определен формат для сообщений?
Такой стандарт у нас есть, и называется он JWT.
Часть третья, пару слов про JWT
JWT (JSON Web Token) – это стандарт, по которому определено, в каком виде будет выглядеть токен для клиента. По сути JWT это строка, состоящая из трех частей соединенных точками: заголовок, данные и подпись.
Например: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
Этот токен содержит:
заголовок (Header)
{
"alg": "HS256",
"typ": "JWT"
}
данные (Payload)
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
и подпись (Signature):
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
Польза от JWT в том, что это стандарт и для этого стандарта уже готовы библиотеки на всевозможных платформах.
К этой статье приложена реализация JWT на чистом 1C:Enterprise 8.3.10.2168.
Например, вы делаете интеграцию со сторонним сервисом, и вам необходимо договорится об авторизации запросов от пользователей. С большой вероятностью сторонний сервис реализован на платформе, для которой есть готовая библиотека JWT. Вы как прогрессивный разработчик 1С теперь можете предложить использовать стандарт RFC 7519 – т.е. JWT. Каждый со своей стороны возьмет готовую библиотеку и вуаля – пользователи ходят напрямую в сторонний сервис.
Заключение
Благодарю, что прочитали эту статью. Надеюсь, вы нашли для себя полезное.
Пожалуйста, напишите в комментариях, как по вашему мнению можно было бы использовать JWT в мире 1С уже сейчас.
Любые, возможно бредовые, идеи принимаются. Например: отправка whatsapp-сообщений прямо из тонкого клиента.
спасибо
Подскажите, пожалуйста, вариацию если в качестве соли используется не строковый ключ, а сертификат
(3) функции хэширования и подписи работают с двоичными данными, строки — это лишь частный случай двоичных данных. Если вы посмотрите внимательно код — там нет фукнкций работы со строками, там используются функции работы с двоичными данными.
Для MD5 и SHA-1 у вас не верный размер блока, он должен быть 64. Можете проверить в любом онлайн генераторе.
И последнее, если ключ больше размера блока, его необходимо предварительно хешировать
(5)
да, вы правы, спасибо.
(6) Обновил код по вашим замечаниям. Благодарю, что нашли ошибки.
задача в интеграции сhttps://docs.veeroute.com/api/examples/json_examples/
есть что-то общее между JWT?
застрял на функции getBytes() какой аналог в 1с (двоичные данные?)
направьте куда копать.
(9) да
в строчке
по всей видимости следует использовать глобальный метод ПолучитьДвоичныеДанныеИзСтроки (GetBinaryDataFromString)
Затем вам нужно будет конвертировать массив в двоичные данные
Для этого вам понадобится объект ЗаписьДанных (DataWriter)
и метод ЗаписатьБайт (WriteByte)
Далее нужно будет соеденить passBytes, saltBytes и вычислить хэш
Для соединения passBytes, saltBytes используйте функцию из статьи
А вот с хэшем беда.
В 1С есть только хэш-функции
CRC32
MD5
SHA1
SHA256
Поэтому ищем в интернете варианты. Например в этой статье есть два подхода для получения SHA-512:
https://infostart.ru/public/175332/
Осталось только полученный хэш конвертировать в массив — для этого используйте объект ЧтениеДанных (DataReader).
(10) Спасибо огромное!!!
byte[] saltBytes = listToArray(ticket.getSalt());
Для этого вам понадобится объект ЗаписьДанных (DataWriter)
и метод ЗаписатьБайт (WriteByte)
Показать
ЗаписатьБайт выдаёт ошибку:
по причине:
Значение байта должно быть в диапазоне от 0 до 255,
а в передаваемом массиве [-100,-96,20,51,89,-47,-80,-128,86,43,-24,124,-109,12,47] есть элементы с минусовым значением…
(11) Оказывается байт java (-127…128), вот и вся ошибка.
(10)
Function BinConcat(Val BinaryData1, Val BinaryData2)
штатным образом можно получить через СоединитьДвоичныеДанные
(10)
В 1С есть только хэш-функции
CRC32
MD5
SHA1
SHA256
Поэтому ищем в интернете варианты. Например в этой статье есть два подхода для получения SHA-512:
https://infostart.ru/public/175332/
Да, действительно беда.
Но можно и вот так попробовать:
Показать
При использовании алгоритма RS256, при декодировании постоянно не совпадают сигнатуры. /:
Подскажите есть ли замена платформенного метода Buffer1.WriteBitwiseXor(0, Buffer2, Buffer2.Size) на аналог
Показать
(16) К сожалению, у меня нет готового кода, которы бы заменял метод WriteBitwiseXor
Выдает ошибку:
Процедура или функция с указанным именем не определена (ValueIsNotFilled)
Блин капец:
Обработка.HMAC_JWT.Форма.Form.Форма Возможно ошибочный метод: «DecodeHS256»
Обработка.HMAC_JWT.Форма.Form.Форма Возможно ошибочный метод: «DecodeRS256»
Обработка.HMAC_JWT.Форма.Form.Форма Возможно ошибочный метод: «EncodeHS256»
Обработка.HMAC_JWT.Форма.Form.Форма Возможно ошибочный метод: «EncodeRS256»
Нет таких функций!
Вы бы перепроверили все. И раз уж на то пошло, то выкладывали бы сразу все в виде одного файла демонстрационной конфигурации или все функции в обработку засунули, а не кучу разрозненных файлов
Я так понимаю есть только Encode и Decode и кодирование производится только по алгоритму SHA256. Все верно?
(18)
Пожалуйста добавьте метод
(19) код скаченных модулей Cryptography и JWT рабочий.
Демонстрационная обработка использует интерфейс от несколько обновленного модуля JWT.
В связи с этим обновил публикацию — залил последнюю версию модуля JWT.
Прошу прощения за неудобства.
Вышлю вам последнюю версию в ЛС.
(20) средствами 1С реализован только алгоритм HS256. Для алгоритма RS256 я использовал COM-объект из сторонней библиотеки.
(23) Хорошо, но из обработки идет вызов функций которых нет в общих модулях, это сбивает с толку. Тогда переименовали бы в общем модуле функции в функции с префиксом HS256, а для RS256 сделали бы «заглушку» с комментариями. Или переименуйте вызываемые функции в самой обработке, удалите те, которых нет на самом деле. Да и было бы отлично, если привести готовый код для RS256 который вы использовали при работе со сторонней библиотекой, я думаю никто бы не огорчился, а только благодарны были бы. А то начинаешь сомневаться в том правильно ли все понял, отнимает время что бы разобраться что к чему и как правильно пользоваться.
В целом публикация хорошая. Написанная Вами библиотека функций очень нужная!
(22) Спасибо большое! Теперь все работает.
Все работает! Спасибо!
Подскажите почему не работает. Сейчас HMAC получаю так
HMAC = 7a619c6b8f79b95fc359a990075d6aeb2f6a6965
А через эту обработку получает 9E83F1A022F0F916580132CA54639DA576E573BC
(28)
https://www.freeformatter.com/hmac-generator.html#ad-output
проверьте, пожалуйста здесь
А можете ли Вы (автор) сделать реализацию pbkdf2? Тема Вам близка а реализации под 1C не нашел.
(30) Я бы с удовольствием. К сожалению, не понимаю для каких практических задач нужен pbkdf2 в 1С. Например HMAC нужен был для интеграции с Twilio. И чтобы избавится от обращения к COM-объекту был реализован JWT и HMAC средствами платформы.
Для каких реальных задач вы видите необходимость в реализации pbkdf2?
(31)
1. Передача данных для проверки PIN кодов в удаленной системе (сейчас партнер передает Base64 строку с хешем солью и параметрами, из за отсутствия реализации проверяем через его API, сначала планировалось локально).
2. Хранение паролей для внешних пользователей, куча решений когда внешних пользователей хранят как обычных. Я в решениях хранил в отдельном справочнике и по простому хешировал пароль.
(32) По второму кейсу в простого хэша может быть и достаточно.
По первому кейсу конечно нужна реализация pbkdf2.
Не могу обещать, что сделаю в скором времени.
Если руки дойдут – оповещу Вас.
Добрый день! По поводу использования такого стандарта 1С. Если вот такой пример. Пусть надуманный, но недалеко от реального использования.
Есть торговая сеть, состоящая из независимых организаций какого-нибудь торгового бренда, сети. Организации независимые (разные юридическое лица) и имеют свои базу 1С пусть «1С:Управление торговлей», но доработки у каждого свои , учитывающие своим местные особенности, свои программисты.
Так как организации представляют один брэнд, они хотят при отсутствии товару у себя, посмотреть у своих коллег в других регионах. Так как единой общей базы нет, то решили сделать следующее: пользователи баз 1С могут зайти в личный веб-кабинет и там увидеть остатки других организаций в других регионах. Для этого используется технология http-запросов или soap-запросов, при этом конечно в самих базах 1С заведены пользователи только «свои», не чужих регионов.
В своей родной «базе» 1С реализован механизм выдаче токена пользователю для веб-кабинета c правами на просмотр разной информации. Между базами 1С произведен обмен SecretKey. В каждой базе 1С реализовали веб-сервис, который получает Token и уже разобрав токен, понимается какие права данного пользователя, время токена и т.п.
Возможно, такое использование?
(34) Да, отличный пример!
Благодаря JWT нет необходимости всем участникам сети делать в своих базах внешних пользователей.
Я вижу процесс так:
Участник сети выдает веб-кабинету свой SecretKey.
Веб-кабинет с помощью SecretKey делает токен.
Другой участник сети с помощью токена может сходить в базу первого участника сети за остатками.
Первый участник сети по токену и SecretKey может определить выполнять запрос или нет.
Токен не дает перманентный доступ — у него есть срок годности.
Чтобы такую систему запустить нужно всем добавить в свои базы функционал:
1. Генерация SecretKey и отправка его в Веб-кабинет
2. HTTP-сервис, который умеет проверять токены и отдавать остатки
Это можно реализовать через расширение.
Ещё нужно сделать веб-кабинет, который фактически может не иметь веб-интерфейса. Достаточно сделать HTTP-сервис, который умеет принимать SecretKey и выдавать токены.
(35) Один момент, который хотелось бы уточнить, нужно ли передавать веб-кабинету SecretKey, почему SecretKey нельзя передавать между базами 1С просто и там хранить?.
А если веб-кабинет, сделан на чистом js (есть фрейворки сейчас даже таки) с запросами ajax, т.е. весь веб-часть это клиентская часть (т.е. там хранить SecretKey не надо)?
Т.е. выдача токена в базе 1С с помощью SecretKey «базы 1», а в другой базе 1С «базы 2» проверка этого токена с помощью SecretKey, т.е. база1 обменялось с базой2 SecretKey (конечно, если SecretKey по-хорошему для каждой базы 1С надо делать свой, чтобы в случае проблем можно было быстро поменять SecretKey и ранее выданные токены перестали работать).
(36) Можно так сделать как вы описываете, но тогда JWT теряет свой смысл, потому что все смогут бесконтрольно делать токены.
Если токены выдавать централизовано, то можно ограничить количество токенов, чтобы пользователи не злоупотребляли возможностью видеть остатки у других пользователей.
В токене ведь кроме срока его действия можно хоть конкретный код номенклатуры указать.
А можно получить данную библиотеку? Не получилось найти на просторах интернета. Спасибо.
(38) Да, конечно. В конце статьи есть кнопка «Скачать». Используйте, пожалуйста, её.
(39) CF скачал, но я имел ввиду про стороннюю библиотеку, используемую Вами «AddIn.RSAUtils» Отдельной ссылки нигде не нашел.
(40) по поводу AddIn.RSAUtils — напишите, мне пожалуйста на почту vasily@pintov.ru