0

I'm trying to validate the contents of a webhook payload from the whatsapp webhooks using the steps in the .Facebook developer docs,

I dont understand what this mean

Please note that we generate the signature using an escaped unicode version of the payload, with lowercase hex digits. If you just calculate against the decoded bytes, you will end up with a different signature. For example, the string äöå should be escaped to \u00e4\u00f6\u00e5.

that why I've always got false when comparing hash value And i want to make it in php/laravel.

after sometimes i tried to use

            $knownSignature = (new UnicodeString($request->getContent()))->normalize(UnicodeString::NFKC);

and

$knownSignature = Str::ascii($request->getContent());

But still doesnt match. event when i tried to convert äöå it still outputting \u00e4\u00f6\u00e5

This is Symfoni and Laravel docs for escape unicode string

Blue Moon
  • 31
  • 9
  • check this [webhook example in nodejs](https://developers.facebook.com/docs/whatsapp/sample-app-endpoints) – turivishal Aug 12 '22 at 04:45
  • Why are you calling this `$knownSignature`, the request content _is not_ the signature, it is only the input data for the signature _calculation_. And you should use the actual, raw data you received for this - and not be trying to manipulate it using any normalize or ascii methods. – CBroe Aug 12 '22 at 07:05
  • 1. Generate a SHA256 signature using the payload and your app's App Secret. 2. Compare your signature to the signature in the X-Hub-Signature-256 header (everything after sha256=). If the signatures match, the payload is genuine. that's what writen in the docs, so take the payloads to normalize it hashing it, and compare it to the signature in the header – Blue Moon Aug 12 '22 at 07:13
  • @turivishal it doesn't give an example to validate the payloads – Blue Moon Aug 12 '22 at 07:14
  • @CBroe [Function](https://stackoverflow.com/questions/73329296/using-symfony-unicodestring-on-laravel) thats my function please correct me – Blue Moon Aug 12 '22 at 07:17
  • Like I said - use the _raw_ POST body data you received as input data for your signature calculation, and not anything that you _modified_ in any way. If `$request->getContent()` doesn't give you access to the raw body, then read it from `php://input`. – CBroe Aug 12 '22 at 07:22
  • @CBroe thanks, I've changed it to `php://input` , but still doesn't get the right value – Blue Moon Aug 12 '22 at 07:30
  • _"I've changed it to php://input"_ doesn't really tell us what _exactly_ you did now. Show updated code. – CBroe Aug 12 '22 at 07:34
  • @CBroe [Code](https://pastebin.com/8xPtHW1C) this is the new code – Blue Moon Aug 12 '22 at 07:41
  • Okay, and what part of _use the **raw** data_ don't you understand? Why are you still applying `UnicodeString::normalize` to it? – CBroe Aug 12 '22 at 07:49
  • @CBroe because in the facebooks docs said this _Please note that we generate the signature using an escaped unicode version of the payload, with lowercase hex digits. If you just calculate against the decoded bytes, you will end up with a different signature. For example, the string äöå should be escaped to \u00e4\u00f6\u00e5_ – Blue Moon Aug 12 '22 at 07:58
  • The request body content _is_ the "escaped unicode version of the payload." Facebook is telling you there, that you should not use any decoded and then re-encoded version of this, but the actual data you received, without _any_ further manipulation. – CBroe Aug 12 '22 at 08:18
  • @CBroe is that mean the signature in the header is the escaped version of the payload ? so to validate it we need to escape the payload first before hashing it ? Or what? I'm sorry my english is not that good – Blue Moon Aug 12 '22 at 09:09
  • The signature from the header, is the value you need to compare your calculated signature to. _"so to validate it we need to escape the payload first before hashing it ?"_ - no. Use it _exactly_ as you have received it, do not manipulate _anything_ about it. – CBroe Aug 12 '22 at 09:21
  • @CBroe I've tried that too, after getting the raw body like [this](https://pastebin.com/8xPtHW1C) but it still returns false/could not verify – Blue Moon Aug 12 '22 at 09:31
  • Well then do a bit of debugging, and check what `$data_body` contains here now to begin with. And $known_signature and $signature_parts[1] as well. – CBroe Aug 12 '22 at 09:35
  • @CBroe I've been debugging it the content of `$request->getContent` is raw Request, but it still gives the invalid signature, i used [this](https://modess.io/2013/07/25/get-raw-post-data-in-laravel/) as a reference too – Blue Moon Aug 15 '22 at 01:09

2 Answers2

2

The answer @Blue Moon gave does compare strings with the === operator, which is not safe to do when comparing user-provided strings / hashes. You should use hash_equals() (https://www.php.net/manual/en/function.hash-equals.php) to avoid timing attacks!

Also, @Patel TiLAK - using $request->getContent() does work, maybe you did not use the correct App Secret?

private function verifySignature(Request $request)
{
    $body = $request->getContent();
    $requestHash = 'sha256=' . hash_hmac('sha256', $body, config('services.whatsapp.app_secret'));

    if (!hash_equals($requestHash, $request->header('X-Hub-Signature-256'))) {
        return false;
    }

    return true;
}
felixfrey
  • 61
  • 3
  • 1
    Thanks @felixfrey. I was using WebHook verification secret rather than AppSecret hence it was not working. Its working now :) – Patel TiLAK Jan 05 '23 at 11:58
1

I've done just like @CBroe said it did not work in my previous function, but when I remake it like this its works

protected function validatePayloads(string $waSignature,string $payloads){
    $receivedSignature = explode('=', $waSignature)[1];

    $generatedSignature = hash_hmac(
        'sha256',
        $payloads,
        config('app.app_secret')
    );

    if($receivedSignature == $generatedSignature){
        return true;
    }else{
        return false;
    }
}

Just like @CBroe said you need to hash the raw request

Blue Moon
  • 31
  • 9
  • Hi @blue-moon. Can you paste how you are getting $payloads ? That is the most crucial part. Using `request->getContent()` directly gives different hash. – Patel TiLAK Dec 27 '22 at 05:28
  • As mentioned by @felixfrey, make sure you are using AppSecret & not WebHook verification secret that you created. request->getContent() does work properly. – Patel TiLAK Jan 05 '23 at 12:00