Проверка подписи

Результаты серверных запросов (методы getMe, getPhone и getContacts) содержат специальное поле sign.

С помощью этого поля серверная сторона вашего приложения может убедиться, что клиент отправил данные, действительно полученные со стороны Aitu Bridge.

В конце статьи предоставлены референсные имплементации для проверки подписи на некоторых серверных языках, которые можно скопировать и использовать на вашем сервере.

Если у ответа нет поля sign, то возможность проверить подлинность отсутствует. В таком случае относитесь к клиентским данным с опаской.

Подлинность данных гарантируется тем, что API-ключ для подписывания данных хранится только в Aitu и на стороне вашего сервера.

Не передавайте API-ключ третьим лицам!

В случае подозрения на компрометацию API-ключа, следует как можно скорее сформировать новый в личном кабинете. Предыдущий при этом деактивируется.

Алгоритм проверки достоверности данных

Для того, чтобы убедиться в достоверности полученных данных, необходимо следовать следующему алгоритму:

  1. Получить результат ответа API-метода — некий JSON-объект.

  2. Исключить из этого результата поле sign, после чего у него (и у всех вложенных объектов) исключить ключи с false-значениями (0, null , false, пустой строкой ""), пустыми массивами [] или пустыми объектами {}. Затем преобразовать его в строку вида ключ1:значение1ключ2:значение2ключ3:значение3, где все ключи объектов отсортированы по алфавиту.

  3. Используя алгоритм проверки целостности HMAC с хеш-функцией sha256 и API-ключом, получить хеш строки, сформированной на предыдущем шаге.

  4. Полученный хеш закодировать в base64-строку (base64url-вариант, с заменой + и / на - и _ соответственно, с сохранением возможных = в конце).

  5. Сравнить результат предыдущего шага с полем sign пришедшего объекта.

При формировании строки для хеширования следует исключить из нее ключ sign.

Ключи вложенных объектов, а также объектов находящихся в списках, также должны сортироваться в алфавитном порядке и добавляться к строке последовательно согласно порядку сортировки их родительских ключей.

Ключи объектов со значениями 0,null, false или "" (пустая строка), [] (пустой массив), {} (пустой объект) опускаются.

В качестве хеш-функции для HMAC необходимо использовать sha256.

Если значение сформированного хеша в 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=

Для тестирования генерации HMAC можно воспользоваться online-сервисами. Например, https://www.devglan.com/online-tools/hmac-sha256-online с выводом результата в base64-формате. Обратите внимание, что полученный в этом сервисе хеш нужно дополнительно привести в base64url-формат, заменив все + на -, а / на _.

Полученный хеш совпадает с полем 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

#407:

Change request updated