Проверка подписи
Результаты серверных запросов (методы getMe, getPhone и getContacts) содержат специальное поле sign.
С помощью этого поля серверная сторона вашего приложения может убедиться, что клиент отправил данные, действительно полученные со стороны Aitu Bridge.
В конце статьи предоставлены референсные имплементации для проверки подписи на некоторых серверных языках, которые можно скопировать и использовать на вашем сервере.
Если у ответа нет поля sign, то возможность проверить подлинность отсутствует. В таком случае относитесь к клиентским данным с опаской.
Подлинность данных гарантируется тем, что API-ключ для подписывания данных хранится только в Aitu и на стороне вашего сервера.
Не передавайте API-ключ третьим лицам!
В случае подозрения на компрометацию API-ключа, следует как можно скорее сформировать новый в личном кабинете. Предыдущий при этом деактивируется.
Алгоритм проверки достоверности данных
Для того, чтобы убедиться в достоверности полученных данных, необходимо следовать следующему алгоритму:
Получить результат ответа API-метода — некий JSON-объект.
Исключить из этого результата поле sign, после чего у него (и у всех вложенных объектов) исключить ключи с false-значениями (
0
,null
,false
, пустой строкой""
), пустыми массивами[]
или пустыми объектами{}
. Затем преобразовать его в строку видаключ1:значение1ключ2:значение2ключ3:значение3
, где все ключи объектов отсортированы по алфавиту.Используя алгоритм проверки целостности HMAC с хеш-функцией sha256 и API-ключом, получить хеш строки, сформированной на предыдущем шаге.
Полученный хеш закодировать в base64-строку (base64url-вариант, с заменой
+
и/
на-
и_
соответственно, с сохранением возможных=
в конце).Сравнить результат предыдущего шага с полем sign пришедшего объекта.
Если значение сформированного хеша в base64url совпадает с полем sign полученного объекта, то данные достоверны.
Пример
Пусть API-ключ имеет значение my_secret_key
. Предположим, при вызове метода getContacts был получен следующий JSON-объект (для демонстрации в нем намеренно перемешаны ключи и добавлены ключи с опускаемыми значениями):
{
"empty_string_key": "",
"sign": "tdMk-vw3bTMPDMldnx4MgCbdJJNH2B60LizMzHv_De4=",
"contacts": [
{
"last_name": "pupkin",
"phone": "7991118837",
"first_name": "vasya",
"null_key_deep": null
},
{
"first_name": "john",
"last_name": "doe",
"phone": "79992222210"
},
{
"first_name": "kavychka",
"last_name": "\"",
"phone": "79992222211"
}
],
"zero_key": 0,
"null_key": null,
"false_key": false,
"empty_array": [],
"empty_object": {}
}
Строка для хэширования, сформированная по описанному выше алгоритму, должна получиться следующей:
contacts:first_name:vasyalast_name:pupkinphone:7991118837first_name:johnlast_name:doephone:79992222210first_name:kavychkalast_name:"phone:79992222211
Результат формирования HMAC-хеша (на базе алгоритма sha256) этой строки с ключом my_secret_key
с кодированием в base64url должен получиться следующим:
tdMk-vw3bTMPDMldnx4MgCbdJJNH2B60LizMzHv_De4=
Полученный хеш совпадает с полем sign исходного объекта, а значит данные подлинные.
Референсные имплементации
function isValidSign(fullBridgeResponse, secretKey) {
if (
typeof fullBridgeResponse !== 'string' && typeof fullBridgeResponse !== 'object'
|| typeof secretKey !== 'string'
) {
throw new Error('Can accept string or object as first argument and string as second argument only');
}
const { sign: inputSign, ...bridgeResponse } = typeof fullBridgeResponse === 'object'
? fullBridgeResponse
: JSON.parse(fullBridgeResponse);
const hmac = require('crypto').createHmac('sha256', secretKey);
const base64URLUnsafeHash = hmac.update(makeStringToHash(bridgeResponse)).digest('base64');
const calculatedSign = base64URLUnsafeHash.replace(/\+/g, '-').replace(/\//g, '_');
return inputSign === calculatedSign;
function makeStringToHash(input) {
if (Array.isArray(input)) {
return input.map(makeStringToHash).join('');
} else if (typeof input === 'object') {
return Object.keys(input)
.filter((key) => input[key] && (!Array.isArray(input[key]) || input[key].length > 0) && (typeof input[key] !== 'object' || Object.keys(input[key]).length > 0))
.sort()
.map((key) => `${key}:${makeStringToHash(input[key])}`)
.join('');
} else {
return String(input);
}
}
}
const bridgeResponseJSONExample = '{"empty_string_key":"","sign":"tdMk-vw3bTMPDMldnx4MgCbdJJNH2B60LizMzHv_De4=","contacts":[{"last_name":"pupkin","phone":"7991118837","first_name":"vasya","null_key_deep":null},{"first_name":"john","last_name":"doe","phone":"79992222210"},{"first_name":"kavychka","last_name":"\\"","phone":"79992222211"}],"zero_key":0,"null_key":null}';
const secretKeyExample = 'my_secret_key';
console.log(isValidSign(bridgeResponseJSONExample, secretKeyExample)); // Should be true
Для упрощения тестирования вашей собственной реализации подписи мы подготовили интерактивную форму на codepen.io
Last updated
Was this helpful?