Python version Google Advertising admob server callback verification SSV

Google's incentive advertising Google callback server side validation (SSV). Python version is based on the third-party package ecdsa out of the box.

Address of Google public key:

https://www.gstatic.com/admob/reward/verifier-keys.json

be careful:

  1. The public key provided by AdMob key server will rotate from time to time. To ensure that SSV callbacks can continue to be verified as expected, do not cache the public key for more than 24 hours.
  2. Google expects your server to return the HTTP 200 OK success status response code for the SSV callback. If your server is unreachable or does not provide the expected response, Google will try to send the SSV callback again, up to 5 times every 1 second.
  3. Use the key in the callback parameter_ ID takes the corresponding public key for signature verification.

To install the ecdsa package:

$ pip install ecdsa

The complete code is as follows, python 3 version:

# codin=utf8
"""
google admob server side verify

pip install ecdsa

success
"""
import sys
import json
import urllib.parse
import urllib.request
import base64
import hashlib

from ecdsa.keys import VerifyingKey, BadSignatureError
from ecdsa.util import sigdecode_der

# AdMob key server
VERIFIER_KEYS_URL = 'https://www.gstatic.com/admob/reward/verifier-keys.json'
# Simulation browser information of request
USER_AGENT = '''
Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36
'''


def extract_verifier_keys():
    """
    //Extract the public key list used to verify the SSV callback of incentive video advertisement from google's AdMob key server.
    //Public key list in JSON format
    """
    keys_dict = dict()
    response = urllib.request.urlopen(VERIFIER_KEYS_URL)
    if response.status != 200:
        return keys_dict

    json_keys = response.read().decode('utf-8')
    if not json_keys:
        return keys_dict

    data_keys = json.loads(json_keys)
    if not data_keys or 'keys' not in data_keys or not isinstance(data_keys, dict):
        return keys_dict

    for key_ids in data_keys['keys']:
        keys_dict[str(key_ids['keyId'])] = dict(
            pem=key_ids['pem'],
            base64=key_ids['base64']
        )

    return keys_dict


def parse_query_string(query_string: str):
    """
    //Parse query_string, take out signature, key_id´╝îmessage
    :param query_string:
    :return:
    """
    query_dict = dict(
        signature='',
        key_id='',
        message=''
    )
    if not query_string or query_string.find('&') == -1:
        return query_dict

    query_string_dict = dict([x.split('=', 1) for x in query_string.split('&')])
    query_dict['signature'] = query_string_dict.get('signature').strip() or ''
    query_dict['message'] = query_string[:query_string.index('signature') - 1].strip() or ''
    query_dict['key_id'] = query_string_dict.get('key_id', '').strip() or ''

    return query_dict


def signature_verifier(ver_message: str, ver_signature: str, public_key_pem: str):
    """
    //Verification signature
    :param ver_message: Signature string
    :param ver_signature:  base64 Encoded signature
    :param public_key_pem: Public key
    :return: boolean
    """
    if not all([ver_message, ver_signature, public_key_pem]):
        return False

    public_key = VerifyingKey.from_pem(public_key_pem)
    # Note the substitutions and complements here
    ver_signature = base64.urlsafe_b64decode(str(ver_signature) + '===')
    ver_message = bytes(ver_message, encoding="utf8")

    try:
        return public_key.verify(
            ver_signature,
            ver_message,
            hashfunc=hashlib.sha256,
            sigdecode=sigdecode_der,
        )
    except BadSignatureError as e:
        return False


if __name__ == '__main__':
    # Google callback parameter, get parameter
    query_string = """123456"""

    # Parse query_string
    query_dict = parse_query_string(query_string)
    signature = query_dict.get('signature').strip()  # Signature result string
    message = query_dict.get('message').strip()  # metadata
    key_id = query_dict.get('key_id').strip()  # Public key id used for signing 

    # Get the public key of Google AdMob
    verifier_keys = extract_verifier_keys()
    if not verifier_keys or key_id not in verifier_keys:
        sys.exit('Fetch google admob keys failed!')

    # According to the google callback key_id takes the corresponding public key
    pem_keys = verifier_keys.get(str(key_id), None)
    if not pem_keys:
        sys.exit('Can not found public key by key_id!')

	# Perform verification
    ret_verifier = signature_verifier(message, signature, pem_keys['pem'])
    print(ret_verifier)

###To execute a python script:

$ python depakin.py
True

True verification succeeded.

Tags: Programming Google JSON Python pip

Posted on Fri, 22 May 2020 08:20:02 -0700 by amargharat