Python版本Google廣告admob服務端回調驗證SSV

Google的激勵廣告的 Google 回調的服務器端驗證 ( SSV ) server side verifiy。Python版本的基於第三方包 ecdsa 開箱即用。python

Google公鑰的地址:

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

注意:json

  1. AdMob 密鑰服務器提供的公鑰會不按期輪換。爲確保能夠繼續按預期驗證 SSV 回調,請勿使公鑰的緩存時間超過 24 小時。
  2. Google 預計您的服務器會針對 SSV 回調返回 HTTP 200 OK 成功狀態響應代碼。若是您的服務器沒法訪問或未提供預期的響應,Google 將從新嘗試發送 SSV 回調,每隔 1 秒發送最多 5 次。
  3. 用回調參數中key_id 取對應公鑰,進行簽名驗證。

安裝ecdsa包:

$ pip install ecdsa

完整代碼以下, python3版本:

# 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密鑰服務器
VERIFIER_KEYS_URL = 'https://www.gstatic.com/admob/reward/verifier-keys.json'
# 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():
    """
    從google的AdMob密鑰服務器提取用於驗證激勵視頻廣告SSV回調的公鑰列表。
    公鑰列表以 JSON 格式
    """
    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):
    """
    解析query_string,取出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):
    """
    校驗簽名
    :param ver_message: 簽名字符串
    :param ver_signature:  base64編碼的簽名
    :param public_key_pem: 公鑰
    :return: boolean
    """
    if not all([ver_message, ver_signature, public_key_pem]):
        return False

    public_key = VerifyingKey.from_pem(public_key_pem)
    # 注意這裏的 替換 和補位
    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 回調參數,get傳參
    query_string = """123456"""

    # 解析query_string
    query_dict = parse_query_string(query_string)
    signature = query_dict.get('signature').strip()  # 簽名結果字符串
    message = query_dict.get('message').strip()  # 元數據
    key_id = query_dict.get('key_id').strip()  # 簽名使用的公鑰id 

    # 獲取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!')

    # 根據google回調的key_id 取對應的公鑰
    pem_keys = verifier_keys.get(str(key_id), None)
    if not pem_keys:
        sys.exit('Can not found public key by key_id!')

	# 執行校驗
    ret_verifier = signature_verifier(message, signature, pem_keys['pem'])
    print(ret_verifier)

### 執行python腳本:瀏覽器

$ python depakin.py
True

True 校驗成功緩存

相關文章
相關標籤/搜索