Проверка HMAC с помощью пользовательского бота Microsoft Teams в PHP

Я пытаюсь аутентифицировать настраиваемого бота Microsoft Teams с помощью PHP в соответствии с Microsoft инструкции и прочитайте пример кода C #.

Шаги Microsoft Intructions:
1. Создайте hmac из тела запроса сообщения. На большинстве платформ есть стандартные библиотеки. Microsoft Teams использует стандартную криптографию SHA256 HMAC. Вам нужно будет преобразовать тело в байтовый массив в UTF8.
2. Чтобы вычислить хэш, предоставьте байтовый массив общего секрета.
3. Преобразуйте хеш в строку, используя кодировку UTF8.
4. Сравните строковое значение сгенерированного хэша со значением, указанным в HTTP-запросе.

Мне пришлось написать небольшой скрипт php, чтобы проверить это на местном уровне:

        <?php
        //Function to generate C# byte[] equivalent
        function unpak_str($val){
            $b = unpack('C*', $val);
            foreach ($b as $key => $value)
                $byte_a .= $value;

           return $byte_a;
          }

        //multi test outputs
        function hasher($values=[], &$output){
            //my secret share
            $secret="ejWiKHgsKY1ZfpJwJ+wIiN4+bgsFad/lkpu9/MWNXgM=";
            //diferent test
            $secret_64=base64_decode($secret);
            $secret_b=unpak_str($secret);
            $secret_b_64=unpak_str(base64_decode($secret));

            foreach($values as $msg){
                $hs = hash_hmac("sha256",$msg,$secret, true);
                $hs_64 = hash_hmac("sha256",$msg,$secret_64, true);
                $hs_b = hash_hmac("sha256",$msg,$secret_b, true);
                $hs_b_64 = hash_hmac("sha256",$msg,$secret_b_64, true);

                $output.=base64_encode($hs)." <BR>";
                $output.=base64_encode($hs_64)." <BR>";
                $output.=base64_encode($hs_b)." <BR>";
                $output.=base64_encode($hs_b_64)." <BR>";
             }
          }

    //Get data
    $data=file_get_contents('php://input');

    //real data request content for test
    $data ='{type":"message","id":"1512376018086","timestamp":"2017-12-04T08:26:58.237Z","localTimestamp":"2017-12-04T09:26:58.237+01:00","serviceUrl":"https://smba.trafficmanager.net/emea-client-ss.msg/","channelId":"msteams","from":{"id":"29:1aq6GCrC6lM9dv3YkAYi1gxTPiLnojGFgVr0_Th-2x6DhqmHAOhFwQHFzSyDy5RruXY4_FZjJebKHU7bpxfHpXA","name":"ROBERTO ALONSO FERNANDEZ","aadObjectId":"1e0dc7a0-9d5e-488b-bcf2-7e39c84076b8"},"conversation":{"isGroup":true,"id":"19:[email protected];messageid=1512376018086","name":null},"recipient":null,"textFormat":"plain","attachmentLayout":null,"membersAdded":[],"membersRemoved":[],"topicName":null,"historyDisclosed":null,"locale":null,"text":"<at>PandoBot</at> fff","speak":null,"inputHint":null,"summary":null,"suggestedActions":null,"attachments":[{"contentType":"text/html","contentUrl":null,"content":"<div><span itemscope=\"\" itemtype=\"http://schema.skype.com/Mention\" itemid=\"0\">PandoBot</span> fff</div>","name":null,"thumbnailUrl":null}],"entities":[{"type":"clientInfo","locale":"es-ES","country":"ES","platform":"iOS"}],"channelData":{"teamsChannelId":"19:[email protected]","teamsTeamId":"19:[email protected]","channel":{"id":"19:[email protected]"},"team":{"id":"19:[email protected]"},"tenant":{"id":"9744600e-3e04-492e-baa1-25ec245c6f10"}},"action":null,"replyToId":null,"value":null,"name":null,"relatesTo":null,"code":null}';


    //generate HMAC hash with diferent $data formats
    $test = [$data, unpak_str($data), base64_encode($data), unpak_str(base64_encode($data))];
    hasher($test, $output);


    //microsoft provided HMAC
    $output.="<HR>EW2993goL1q7nGhytIb3jKmV6luXLz15Bq2aYwuCeiE="; 


    echo $output;
    /*
    Calculates: 
    0HsKoHza/QBvdz+nZw9tOti/eSWjyMMt/U77bfDqiE8=
    3jSq3I0HNQkjB9QfnnsxC1c3pF5PjqweHlSVcicrShY=
    bTQcGVTHX8/Gh4xovnN0WiJUiNaOQwvUZnwyFfiCaJE=
    qHBT2Y2ITyoxz2gmBbG8P1CrClvETus6dTffET3bAR8=
    8BcrXEQDDi77qgxCZLYyb/6ez8p9Qg2ZhTyZPWkdn/g=
    +8RSU5SSJKxqRLKkI+NkTE01xwu6PwPkKKMuvyyUvlo=
    PdL5ZpEwcN6Fe5kfX7zeAZLJvt0uLNTzu7lhuoOcr2o=
    s6M5pYruEgWeNMEOFfQRjVKQqtPBVaW3TJb2MzObF2c=
    xOTLhddbAwczQVneuTDQhPzmoIXGQljpf27c+hlhQII=
    aUMm5b2sKfmwGZOglfiu228fWqoLlwjc7z1QRdIbakE=
    5a7bAj9tzqhP9l85OvfVasURW0GSV5rykRutFFPO2fk=
    kwg6P2LoDL9rc3SSwJxQeoYJzZYlh+FHFefe38UokBM=
    eHeAzI7TV6vYDzxTxwyKWxMeVKFiFlIffWRiIMAk6fk=
    ZCyj2UppacQOTXogLPMFLDeMArQg03rhhlIwhynDvng=
    uQYK+7u9fppb62zXqtVYfkNK9wVawB3g+BlTyu4dc74=
    vjOFA3fqpwUx/VO9dQv3XviNhpjTNQsUwaJIwH4JjdY=
    ------------ MS PROVIDED HMAC ---------------
    EW2993goL1q7nGhytIb3jKmV6luXLz15Bq2aYwuCeiE=
     */

У меня нулевое совпадение хеша ...


person roberto Alonso    schedule 04.12.2017    source источник


Ответы (3)


В конце концов, после долгих испытаний это свело меня с ума и я решил создать нового бота с новым секретом. Теперь работает нормально. Я человек, в то время как команды MS нет ... Полагаю, это была моя ошибка с копированием / вставкой, но это действительно странная вещь, а с другой стороны, старый бот терпит неудачу много раз без ответа, а новейший нет

Полный пример проверки HMAC в PHP для пользовательского бота Microsoft Teams:

        <?php

        //The secret share with Microsoft Teams
        $secret="jond3021g9imMkrt8txF5AVPIwPFouNV/I72cQFii18=";

        //get headers
        $a = getallheaders();
        $provided_hmac=substr($a['Authorization'],5);

        //Get data from request
        $data=file_get_contents('php://input');

        //json decode into array
        $json=json_decode($data, true);

        //hashing
        $hash = hash_hmac("sha256",$data,base64_decode($secret), true);
        $calculated_hmac = base64_encode($hash);

        //start log var
        $log = "\n========".date("Y-m-d H:i:s")."========\n".$provided_hmac."\n".$calculated_hmac."\n";

        try{
            //compare hashs
            if(!hash_equals($provided_hmac,$calculated_hmac))
                throw new Exception("No hash matching");
            //response text
            $txt="Hi {$json["from"]["name"]} welcome to your custom bot";
            echo '{
                "type": "message",
                "text": "'.$txt.'"
                 }';
            $log .= "Sended: {$txt}";
        }catch (Exception $e){
            $log .= $e->getMessage();
        }
        //write log
        $fp = fopen("log.txt","a");
        fwrite($fp, $log . PHP_EOL);
        fclose($fp);
person roberto Alonso    schedule 05.12.2017

Я не эксперт по PHP, и ваша логика для охвата всех случаев немного запутана, но я почти уверен, что ваша проблема в том, что вы не конвертируете сообщение ($ data) из UTF8 перед вычислением HMAC.

Вот простой настраиваемый эхо-бот в Node, который показывает, как вычислять и проверять HMAC:

const util = require('util');
const crypto = require('crypto');
const sharedSecret = "+ZaRRMC8+mpnfGaGsBOmkIFt98bttL5YQRq3p2tXgcE=";
const bufSecret = Buffer(sharedSecret, "base64");

var http = require('http');
var PORT = process.env.port || process.env.PORT || 8080;

http.createServer(function(request, response) { 
    var payload = '';
    request.on('data', function (data) {
        // console.log("Chunk size: %s bytes", data.length)
        payload += data;
    });

    request.on('end', function() {
        try {
            // Retrieve authorization HMAC information
            var auth = this.headers['authorization'];
            // Calculate HMAC on the message we've received using the shared secret         
            var msgBuf = Buffer.from(payload, 'utf8');
            var msgHash = "HMAC " + crypto.createHmac('sha256', bufSecret).update(msgBuf).digest("base64");
            console.log("Computed HMAC: " + msgHash);
            console.log("Received HMAC: " + auth);

            response.writeHead(200);
            if (msgHash === auth) {
                var receivedMsg = JSON.parse(payload);
                var responseMsg = '{ "type": "message", "text": "You typed: ' + receivedMsg.text + '" }';   
            } else {
                var responseMsg = '{ "type": "message", "text": "Error: message sender cannot be authenticated." }';
            }
            response.write(responseMsg);
            response.end();
        }
        catch (err) {
            response.writeHead(400);
            return response.end("Error: " + err + "\n" + err.stack);
        }
    });

}).listen(PORT);

console.log('Listening on port %s', PORT);
person Bill Bliss - MSFT    schedule 05.12.2017
comment
Весь контент был в UTF-8. После вашего ответа я протестировал его с mb_detect_encodeing();, и результат был UTF-8 .... Наконец, я думаю, что это сумасшедшая проблема с генерацией / копированием секрета. Спасибо за помощь! - person roberto Alonso; 05.12.2017
comment
Здорово! Мне было интересно, возможно, произошла ошибка с секретом, но не было никаких доказательств. - person Bill Bliss - MSFT; 05.12.2017

Вам не нужна unpack() или эта unpak_str() функция (которая также не работает, потому что она просто перезаписывает каждый байт следующим, а не добавляет их).

Байтовые массивы не используются в PHP - в языке нет разных строковых типов; то, как интерпретируются строки, полностью зависит от функций, которые их используют. То есть ваш общий секрет должен быть просто результатом base64_encode($secret).

person Narf    schedule 04.12.2017
comment
Привет @Narf. Да, это был мой первый вариант, но я не получил хэш совпадения. Я сделал функцию de hasher(), чтобы протестировать несколько форматов, заданных в $test Array, и вывести четыре разных hash_hmac() с unapck () и без него; - person roberto Alonso; 04.12.2017