如何在外部服务器验证GameKit LocalPlayer身份签名及排查失败问题
我正尝试按照苹果官方文档实现iOS GameKit客户端已认证用户的服务端身份验证。该API从客户端接收Player ID、timestamp和salt,同时返回公钥URL。我尝试从该URL获取证书提取公钥时遇到编码异常,所有解码格式都失败;但读取单独下载到本地的同一份证书却能正常工作,因此目前采用读取本地文件的方式获取证书。
我编写了如下验证流程代码,但签名验证步骤始终失败,抛出cryptography.exceptions.InvalidSignature异常,请问问题出在哪里?我找不到最新的解决方案或代码示例,查阅过10年前的相关帖子但方案已过时,自认为代码是其更新版本。
import struct import base64 import hashlib from cryptography.hazmat.primitives.asymmetric import padding from cryptography import x509 from cryptography.hazmat.primitives import hashes # GameKit public key URL and other params public_key_url = "https://static.gc.apple.com/public-key/gc-prod-9.cer" player_id = <GameKit_TeamPlayerID> bundle_id = <App_Bundle_ID> timestamp = 1683019415788 salt = "OnSojw==" # client-generated signature and payload client_signature = <GameKit_Client_Signature> client_payload =player_id.encode('UTF-8') + bundle_id.encode('UTF-8') + struct.pack('>Q',timestamp) + base64.b64decode(salt) # Download and decode the GameKit public key # Or read from local cer file with open("gc-prod-9.cer", "rb") as f: cert_data = f.read() # Parse the certificate data cert = x509.load_der_x509_certificate(cert_data) # Extract the public key from the certificate public_key = cert.public_key() # Hash the client payload with SHA-256 payload_hash = hashlib.sha256(client_payload).digest() # Verify the client signature using the GameKit public key signature_bytes = base64.b64decode(client_signature) try: public_key.verify( signature_bytes, payload_hash, padding.PKCS1v15(), hashes.SHA256() ) print("Client signature verified successfully") except Exception as e: print("Client signature verification failed") print(str(e))
错误信息:
in _rsa_sig_verify
raise InvalidSignature
cryptography.exceptions.InvalidSignature
问题排查与解决方案
1. 核心错误:重复哈希Payload
你的代码中对Payload做了两次SHA-256哈希,这是导致验证失败的关键:public_key.verify方法会自动对传入的原始Payload进行哈希计算,不需要提前手动哈希。你当前传入的是哈希后的payload_hash,相当于让验证逻辑对哈希结果再做一次哈希,最终和客户端签名的哈希值完全不匹配。
修改后的验证代码应直接传入原始Payload:
try: public_key.verify( signature_bytes, client_payload, # 传入原始Payload,而非提前哈希的结果 padding.PKCS1v15(), hashes.SHA256() ) print("Client signature verified successfully") except Exception as e: print("Client signature verification failed") print(str(e))
2. Payload构造细节校验
- Player ID类型:确认使用的是Team Player ID,而非客户端返回的普通Player ID,两者格式不同,不能混用
- Timestamp单位:客户端返回的timestamp是毫秒级,
struct.pack('>Q', timestamp)要求64位无符号整数,确认数值未超出范围;部分旧版本API使用秒级timestamp,可尝试转换为秒级测试 - Bundle ID一致性:必须和客户端的Bundle ID完全一致,包括大小写、后缀,不能有任何拼写错误
3. 证书加载格式验证
苹果返回的.cer文件通常是DER格式,但如果下载时被浏览器自动转换为PEM格式(带有-----BEGIN CERTIFICATE-----头),用x509.load_der_x509_certificate会解析失败。可尝试替换为PEM加载方法测试:
cert = x509.load_pem_x509_certificate(cert_data)
4. Signature解码预处理
确认客户端传递的client_signature是标准Base64编码,无额外换行或空格,否则会导致解码错误:
client_signature = client_signature.strip().replace('\n', '') signature_bytes = base64.b64decode(client_signature)
5. 可选:证书链验证
为避免伪造证书攻击,建议添加苹果根证书的链验证(需提前下载苹果官方根证书):
from cryptography.hazmat.primitives import serialization # 加载苹果根证书 with open("AppleRootCA-G3.cer", "rb") as f: root_cert_data = f.read() root_cert = x509.load_der_x509_certificate(root_cert_data) # 验证当前证书由苹果根证书签发 cert.verify_directly_issued_by(root_cert)
内容的提问来源于stack exchange,提问作者Santhosh Kumar

