Лёгкая статья про стандарты 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-сообщений прямо из тонкого клиента.