Что такое HMAC и JWT и как это использовать в 1С

Публикация № 611505

Обмен - Обмен с другими системами

HMAC JWT Twilio DocumentDB

89
Лёгкая статья про стандарты HMAC и JWT с небольшой теорией и исходным кодом.

Лёгкая статья про стандарты HMAC и JWT с небольшой теорией и исходным кодом.

Коллеги, позвольте вам рассказать о вещах, которые далеки от обычной разработки в 1С.

Вероятно, что сейчас у вас нет необходимости использовать HMAC и JWT, но время не стоит на месте, и, возможно, в очередном проекте интеграции вы задействуете описываемые ниже стандарты.

Часть первая, теоретическая.

HMAC (Hash-based message authentication code) - это хэш, который вычисляется, основываясь на двух значениях: 'ключ' и 'сообщение'. Такой хэш нужен, чтобы гарантировать, что данные, передаваемые в ненадежной среде, не были изменены посторонними лицами.

Зачем же нам использовать HMAC, когда у нас есть, например, HTTPS?

HMAC полезен, когда участников в обмене сообщениями больше, чем два.

Например, у нас есть три участника:

  •  'Провайдер API' - сторонний сервис, который принимает запросы на отправку открыток и букетов
  •  'Сервер' - серверная часть вашего приложения
  •  'Клиент' - клиентская часть, с которой работают пользователи

'Провайдер API' предоставляет две вещи для выполнения запросов – это AccounID и SecretKey. 'Провайдер API' ни чего не знает про ваших пользователей, чтобы принять запрос на отправку открытки, ему нужно удостовериться, что просящий знает AccounID и SecretKey.

'Сервер' в свою очередь должен управлять тем, какие пользователи имеют право отправлять букеты, а каким разрешены только открытки.

Разумеется, очевидное решение задачи заключается в том, чтобы все клиентские запросы направить через 'Сервер':

  1.  'Клиент' - формирует запрос на отправку открытки для 'Сервера';
  2.  'Сервер' - проверяет права конкретного пользователя и если всё хорошо, то перенаправляет вызов на 'Провайдера API';
  3.  'Провайдер API' делает необходимые действия;
  4. Далее по цепочке обратно передается результат вызова.

Но что, если у нас клиенты генерируют десятки тысяч таких запросов, и не хотят долго ждать результат выполнения? Или наши запросы содержат потоковое аудио (для музыкальных открыток), которым не хотелось бы грузить 'Сервер'? Более того, а что, если 'Клиент' передает конфиденциальные сведения, которые и вовсе не должны попасть на 'Сервер' (букет с интимным посланием)?
Почему бы нам сразу не слать запросы с 'Клиента' на 'Провайдер API'?

Тогда нам придется 'Клиенту' сообщить AccounID и SecretKey, которые нужны 'Провайдеру API'. Но поскольку у нас разные клиенты имеют разные права (открытки, букеты) и в какой то момент у клиента права могут быть и вовсе отозваны, то мы не можем сообщать 'Клиенту' AccounID и SecretKey.
В этот момент нам и пригодится HMAC.
Благодаря HMAC мы можем построить работу следующим образом:

  1. 'Клиент' запрашивает у 'Сервера' специальный Token
  2. 'Сервер' делает 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-сообщений прямо из тонкого клиента.

89

Скачать файлы

Наименование Файл Версия Размер
Конфигурация содержащая код для работы с HMAC и JWT
.cf 22,45Kb
28.01.19
65
.cf 1.2 22,45Kb 65 Скачать

См. также

Специальные предложения

Лучшие комментарии
1. davdykin 17 21.04.17 21:42 Сейчас в теме
Спасибо за информацию, учитывая популярность микросервисов думаю может вполне пригодиться.
ponkratov; keypax; DrAku1a; +3 Ответить
Остальные комментарии
Избранное Подписка Сортировка: Древо
1. davdykin 17 21.04.17 21:42 Сейчас в теме
Спасибо за информацию, учитывая популярность микросервисов думаю может вполне пригодиться.
ponkratov; keypax; DrAku1a; +3 Ответить
2. stas1976 14 26.04.17 11:23 Сейчас в теме
3. NcSteel 04.05.17 20:58 Сейчас в теме
Подскажите, пожалуйста, вариацию если в качестве соли используется не строковый ключ, а сертификат
4. user757186 29.06.17 19:49 Сейчас в теме
(3) функции хэширования и подписи работают с двоичными данными, строки - это лишь частный случай двоичных данных. Если вы посмотрите внимательно код - там нет фукнкций работы со строками, там используются функции работы с двоичными данными.
5. pbazeliuk 1654 17.10.17 10:19 Сейчас в теме
Для MD5 и SHA-1 у вас не верный размер блока, он должен быть 64. Можете проверить в любом онлайн генераторе.
7. keypax 88 18.10.17 10:51 Сейчас в теме
6. pbazeliuk 1654 17.10.17 11:36 Сейчас в теме
И последнее, если ключ больше размера блока, его необходимо предварительно хешировать
8. keypax 88 26.10.17 18:11 Сейчас в теме
(6) Обновил код по вашим замечаниям. Благодарю, что нашли ошибки.
9. moro_as 17.02.18 06:31 Сейчас в теме
задача в интеграции с https://docs.veeroute.com/api/examples/json_examples/
есть что-то общее между JWT?
застрял на функции getBytes() какой аналог в 1с (двоичные данные?)
направьте куда копать.
10. keypax 88 18.02.18 09:52 Сейчас в теме
(9) да
в строчке
byte[] passBytes = password.getBytes();

по всей видимости следует использовать глобальный метод ПолучитьДвоичныеДанныеИзСтроки (GetBinaryDataFromString)

Затем вам нужно будет конвертировать массив в двоичные данные
byte[] saltBytes = listToArray(ticket.getSalt());

Для этого вам понадобится объект ЗаписьДанных (DataWriter)
и метод ЗаписатьБайт (WriteByte)

Далее нужно будет соеденить passBytes, saltBytes и вычислить хэш
byte[] PaS = makeBytesHash(concatenateByteArrays(passBytes, saltBytes), "SHA-512");


Для соединения passBytes, saltBytes используйте функцию из статьи
Function BinConcat(Val BinaryData1, Val BinaryData2)


А вот с хэшем беда.
В 1С есть только хэш-функции
CRC32
MD5
SHA1
SHA256

Поэтому ищем в интернете варианты. Например в этой статье есть два подхода для получения SHA-512:
https://infostart.ru/public/175332/

Осталось только полученный хэш конвертировать в массив - для этого используйте объект ЧтениеДанных (DataReader).
11. moro_as 19.02.18 11:29 Сейчас в теме
(10) Спасибо огромное!!!
Затем вам нужно будет конвертировать массив в двоичные данные
byte[] saltBytes = listToArray(ticket.getSalt());

Для этого вам понадобится объект ЗаписьДанных (DataWriter)
и метод ЗаписатьБайт (WriteByte)


Функция listToArray(Знач Массив) Экспорт
	Перем Результат;
	ПотокВПамяти = Новый ПотокВПамяти();
	ЗаписьДанных = Новый ЗаписьДанных(ПотокВПамяти);	
	Для Каждого Элемент из Массив Цикл
		ЗаписьДанных.ЗаписатьБайт(Число(Элемент));
	КонецЦикла;
	Результат = ПотокВПамяти.ЗакрытьИПолучитьДвоичныеДанные();
	Возврат Результат;
КонецФункции
Показать


ЗаписатьБайт выдаёт ошибку:
по причине:
Значение байта должно быть в диапазоне от 0 до 255,
а в передаваемом массиве [-100,-96,20,51,89,-47,-80,-128,86,43,-24,124,-109,12,47] есть элементы с минусовым значением...
12. moro_as 19.02.18 13:29 Сейчас в теме
(11) Оказывается байт java (-127...128), вот и вся ошибка.
13. moro_as 19.02.18 19:11 Сейчас в теме
(10)
Для соединения passBytes, saltBytes используйте функцию из статьи
Function BinConcat(Val BinaryData1, Val BinaryData2)

штатным образом можно получить через СоединитьДвоичныеДанные
14. moro_as 19.02.18 23:10 Сейчас в теме
(10)
А вот с хэшем беда.
В 1С есть только хэш-функции
CRC32
MD5
SHA1
SHA256

Поэтому ищем в интернете варианты. Например в этой статье есть два подхода для получения SHA-512:
https://infostart.ru/public/175332/


Да, действительно беда.
Но можно и вот так попробовать:

Функция CryptoSHA512(Строка) Экспорт 
	Текст = Новый COMОбъект("System.Text.UTF8Encoding");
	КриптоSHA512 = Новый COMОбъект("System.Security.Cryptography.SHA512Managed");
	Байты = Текст.GetBytes_4(Строка); 
	МассивХэш = КриптоSHA512.ComputeHash_2(Байты).Выгрузить();
	Возврат МассивХэш;
КонецФункции
Показать
15. Varsaavius 18.05.18 12:28 Сейчас в теме
При использовании алгоритма RS256, при декодировании постоянно не совпадают сигнатуры. /:
16. pallid 209 16.01.19 10:34 Сейчас в теме
Подскажите есть ли замена платформенного метода Buffer1.WriteBitwiseXor(0, Buffer2, Buffer2.Size) на аналог
Function BinBitwiseXOR(Val BinaryData1, Val BinaryData2)
	
	MemoryStream = New MemoryStream();
	DataWriter = New DataWriter(MemoryStream);
	
	DataReader1 = New DataReader(BinaryData1);
	DataReader2 = New DataReader(BinaryData2);
	
	Buffer1 = DataReader1.ReadIntoBinaryDataBuffer();
	Buffer2 = DataReader2.ReadIntoBinaryDataBuffer();
	
	If Buffer1.Size > Buffer2.Size Then
		Buffer1.WriteBitwiseXor(0, Buffer2, Buffer2.Size);
		DataWriter.WriteBinaryDataBuffer(Buffer1);
	Else 
		Buffer2.WriteBitwiseXor(0, Buffer1, Buffer1.Size);
		DataWriter.WriteBinaryDataBuffer(Buffer2);
	EndIf;
	
	res = MemoryStream.CloseAndGetBinaryData();
	Return res;

EndFunction

Показать
17. keypax 88 25.01.19 10:48 Сейчас в теме
(16) К сожалению, у меня нет готового кода, которы бы заменял метод WriteBitwiseXor
18. frkbvfnjh 539 28.01.19 15:21 Сейчас в теме
Выдает ошибку:
Процедура или функция с указанным именем не определена (ValueIsNotFilled)
21. keypax 88 28.01.19 16:42 Сейчас в теме
(18)
Пожалуйста добавьте метод
Function ValueIsNotFilled(Value) Export

	Return Not ValueIsFilled(Value);

EndFunction
19. frkbvfnjh 539 28.01.19 15:33 Сейчас в теме
Блин капец:
Обработка.HMAC_JWT.Форма.Form.Форма Возможно ошибочный метод: "DecodeHS256"
Обработка.HMAC_JWT.Форма.Form.Форма Возможно ошибочный метод: "DecodeRS256"
Обработка.HMAC_JWT.Форма.Form.Форма Возможно ошибочный метод: "EncodeHS256"
Обработка.HMAC_JWT.Форма.Form.Форма Возможно ошибочный метод: "EncodeRS256"
Нет таких функций!
Вы бы перепроверили все. И раз уж на то пошло, то выкладывали бы сразу все в виде одного файла демонстрационной конфигурации или все функции в обработку засунули, а не кучу разрозненных файлов
22. keypax 88 28.01.19 17:13 Сейчас в теме
(19) код скаченных модулей Cryptography и JWT рабочий.
Демонстрационная обработка использует интерфейс от несколько обновленного модуля JWT.
В связи с этим обновил публикацию - залил последнюю версию модуля JWT.
Прошу прощения за неудобства.

Вышлю вам последнюю версию в ЛС.
26. frkbvfnjh 539 29.01.19 14:17 Сейчас в теме
(22) Спасибо большое! Теперь все работает.
20. frkbvfnjh 539 28.01.19 15:36 Сейчас в теме
Я так понимаю есть только Encode и Decode и кодирование производится только по алгоритму SHA256. Все верно?
23. keypax 88 28.01.19 17:22 Сейчас в теме
(20) средствами 1С реализован только алгоритм HS256. Для алгоритма RS256 я использовал COM-объект из сторонней библиотеки.
24. frkbvfnjh 539 29.01.19 05:59 Сейчас в теме
(23) Хорошо, но из обработки идет вызов функций которых нет в общих модулях, это сбивает с толку. Тогда переименовали бы в общем модуле функции в функции с префиксом HS256, а для RS256 сделали бы "заглушку" с комментариями. Или переименуйте вызываемые функции в самой обработке, удалите те, которых нет на самом деле. Да и было бы отлично, если привести готовый код для RS256 который вы использовали при работе со сторонней библиотекой, я думаю никто бы не огорчился, а только благодарны были бы. А то начинаешь сомневаться в том правильно ли все понял, отнимает время что бы разобраться что к чему и как правильно пользоваться.
25. frkbvfnjh 539 29.01.19 06:19 Сейчас в теме
В целом публикация хорошая. Написанная Вами библиотека функций очень нужная!
27. frkbvfnjh 539 29.01.19 14:18 Сейчас в теме
28. kloze 14.05.19 11:22 Сейчас в теме
Подскажите почему не работает. Сейчас HMAC получаю так

<?php 
$p_sign = hash_hmac('sha1', '811576.003RUB8290666011119https://www.test.ru2014052019110700003430', pack('H*', '3716699F804BE05E9E8D767ACEB6B88E')); 
echo $p_sign  
?>


HMAC = 7a619c6b8f79b95fc359a990075d6aeb2f6a6965

А через эту обработку получает 9E83F1A022F0F916580132CA54639DA576E573BC
29. keypax 88 14.05.19 11:55 Сейчас в теме
Оставьте свое сообщение