phpfacebookencryptionfacebook-instant-games

Facebook instant game player signature verification fails


I'm currently developing an instant game on Facebook and need to verify that the player info is authentic from facebook. Facebook provides verification through a hashed signature as documented here:

https://developers.facebook.com/docs/games/instant-games/sdk/fbinstant6.2#signedplayerinfo

I followed the steps as outlined but can't ever get the signature to match:

This is the signature:

$_POST['psig'] = 'je3yV8uKmysDrjXv1xp_RY2rTMJLEREM7xj8SGt5HEk.eyJhbGdvcml0aG0iOiJITUFDLVNIQTI1NiIsImlzc3VlZF9hdCI6MTU1MjI1NTc2OCwicGxheWVyX2lkIjoiMjA1OTE4OTA2MDgyMzk4MyIsInJlcXVlc3RfcGF5bG9hZCI6InBkYXRhIn0';

1) Split the signature into two parts delimited by the '.' character.

$first_part = explode('.', $_POST['psig'])[0];
echo $first_part;

// Output:
je3yV8uKmysDrjXv1xp_RY2rTMJLEREM7xj8SGt5HEk

$second_part = explode('.', $_POST['psig'])[1];
echo $second_part;

// Output
eyJhbGdvcml0aG0iOiJITUFDLVNIQTI1NiIsImlzc3VlZF9hdCI6MTU1MjI1NTc2OCwicGxheWVyX2lkIjoiMjA1OTE4OTA2MDgyMzk4MyIsInJlcXVlc3RfcGF5bG9hZCI6InBkYXRhIn0

2) Decode the first part (the encoded signature) with base64url encoding.

echo base64_decode($first_part);

// Output:
���Wˊ�+�5��Qcj�0��DC;�?�G

3) Decode the second part (the response payload) with base64url encoding, which should be a string representation of a JSON object that has the following fields: ** algorithm - always equals to HMAC-SHA256 ** issued_at - a unix timestamp of when this response was issued. ** player_id - unique identifier of the player. ** request_payload - the requestPayload string you specified when calling FBInstant.player.getSignedPlayerInfoAsync.

$payload = base64_decode($second_part);
echo $payload;

// Output:
{"algorithm":"HMAC-SHA256","issued_at":1552255768,"player_id":"2059189060823983","request_payload":"pdata"}

4) Hash the whole response payload string using HMAC SHA-256 and your app secret and confirm that it is equal to the encoded signature.

$check = hash_hmac('sha256', $payload, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', true);
echo $check;

// Output:
n4Q�Kމ`<�?�����tT�~x����L�

I have spent few hours with this and can't figure out what I'm doing wrong!


Solution

  • Finally got it!

    I had 2 mistakes:

    1) When decoding the encoded signature to check against, base64decode URL should be used NOT the regular base64decode. So code in step 2 should be like this:

    $first_part = base64_decode(str_replace(array('-', '_'), array('+', '/'), $first_part));
    

    2) The generated hash should have the ORIGINAL encoded response payload string NOT the decoded one. The doc is not very clear about this. So it should be like this:

    $check = hash_hmac('sha256', $second_part, $app_secret, true);
    

    By doing the above, the decoded signature would equal to the binary data in the generated hash :)

    And finally to check if signature valid: if($first_part == $check){ //valid }