如何在我的“第三方服务器”上validationGKLocalPlayer?

iOS7引入了新的GKLocalPlayer方法generateIdentityVerificationSignatureWithCompletionHandler()

有谁知道如何使用它的好? 我想在苹果服务器端会有一些公共的API ..

这里是你如何使用目标c进行身份validation。 如果你需要在另一种语言,应该是微不足道的翻译。

 -(void)authenticate { __weak GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer]; localPlayer.authenticateHandler = ^(UIViewController *viewController, NSError *error) { if(viewController) { [[[UIApplication sharedApplication] keyWindow].rootViewController presentViewController:viewController animated:YES completion:nil]; } else if(localPlayer.isAuthenticated == YES) { [localPlayer generateIdentityVerificationSignatureWithCompletionHandler:^(NSURL *publicKeyUrl, NSData *signature, NSData *salt, uint64_t timestamp, NSError *error) { if(error != nil) { return; //some sort of error, can't authenticate right now } [self verifyPlayer:localPlayer.playerID publicKeyUrl:publicKeyUrl signature:signature salt:salt timestamp:timestamp]; }]; } else { NSLog(@"game center disabled"); } }; } -(void)verifyPlayer:(NSString *)playerID publicKeyUrl:(NSURL *)publicKeyUrl signature:(NSData *)signature salt:(NSData *)salt timestamp:(uint64_t)timestamp { //get certificate NSData *certificateData = [NSData dataWithContentsOfURL:publicKeyUrl]; //build payload NSMutableData *payload = [[NSMutableData alloc] init]; [payload appendData:[playerID dataUsingEncoding:NSASCIIStringEncoding]]; [payload appendData:[[[NSBundle mainBundle] bundleIdentifier] dataUsingEncoding:NSASCIIStringEncoding]]; uint64_t timestampBE = CFSwapInt64HostToBig(timestamp); [payload appendBytes:&timestampBE length:sizeof(timestampBE)]; [payload appendData:salt]; //sign SecCertificateRef certificateFromFile = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData); // load the certificate SecPolicyRef secPolicy = SecPolicyCreateBasicX509(); SecTrustRef trust; OSStatus statusTrust = SecTrustCreateWithCertificates( certificateFromFile, secPolicy, &trust); if(statusTrust != errSecSuccess) { NSLog(@"could not create trust"); return; } SecTrustResultType resultType; OSStatus statusTrustEval = SecTrustEvaluate(trust, &resultType); if(statusTrustEval != errSecSuccess) { NSLog(@"could not evaluate trust"); return; } if(resultType != kSecTrustResultProceed && resultType != kSecTrustResultRecoverableTrustFailure) { NSLog(@"server can not be trusted"); return; } SecKeyRef publicKey = SecTrustCopyPublicKey(trust); uint8_t sha256HashDigest[CC_SHA256_DIGEST_LENGTH]; CC_SHA256([payload bytes], (CC_LONG)[payload length], sha256HashDigest); //check to see if its a match OSStatus verficationResult = SecKeyRawVerify(publicKey, kSecPaddingPKCS1SHA256, sha256HashDigest, CC_SHA256_DIGEST_LENGTH, (const uint8_t *)[signature bytes], [signature length]); CFRelease(publicKey); CFRelease(trust); CFRelease(secPolicy); CFRelease(certificateFromFile); if (verficationResult == errSecSuccess) { NSLog(@"Verified"); } else { NSLog(@"Danger!!!"); } } 

编辑:

截至2015年3月2日,苹果现在在证书上使用SHA256而不是SHA1。 https://devforums.apple.com/thread/263789?tstart=0

这里是一个C#WebApi服务器端版本:

 public class GameCenterController : ApiController { // POST api/gamecenter public HttpResponseMessage Post(GameCenterAuth data) { string token; if (ValidateSignature(data, out token)) { return Request.CreateResponse(HttpStatusCode.OK, token); } return Request.CreateErrorResponse(HttpStatusCode.Forbidden, string.Empty); } private bool ValidateSignature(GameCenterAuth auth, out string token) { try { var cert = GetCertificate(auth.PublicKeyUrl); if (cert.Verify()) { var csp = cert.PublicKey.Key as RSACryptoServiceProvider; if (csp != null) { var sha256 = new SHA256Managed(); var sig = ConcatSignature(auth.PlayerId, auth.BundleId, auth.Timestamp, auth.Salt); var hash = sha256.ComputeHash(sig); if (csp.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA256"), Convert.FromBase64String(auth.Signature))) { // Valid user. // Do server related user management stuff. return true; } } } // Failure token = null; return false; } catch (Exception ex) { // Log the error token = null; return false; } } private static byte[] ToBigEndian(ulong value) { var buffer = new byte[8]; for (int i = 0; i < 8; i++) { buffer[7 - i] = unchecked((byte)(value & 0xff)); value = value >> 8; } return buffer; } private X509Certificate2 GetCertificate(string url) { var client = new WebClient(); var rawData = client.DownloadData(url); return new X509Certificate2(rawData); } private byte[] ConcatSignature(string playerId, string bundleId, ulong timestamp, string salt) { var data = new List<byte>(); data.AddRange(Encoding.UTF8.GetBytes(playerId)); data.AddRange(Encoding.UTF8.GetBytes(bundleId)); data.AddRange(ToBigEndian(timestamp)); data.AddRange(Convert.FromBase64String(salt)); return data.ToArray(); } } public class GameCenterAuth { public string PlayerId { get; set; } public string BundleId { get; set; } public string Name { get; set; } public string PublicKeyUrl { get; set; } public string Signature { get; set; } public string Salt { get; set; } public ulong Timestamp { get; set; } } 

谢谢,@odyth。 谢谢@Lionel。 我想在这里添加Python版本(基于你的)。 它有一些小缺陷 – 苹果证书没有被validation – 在pyOpenSSL绑定中没有这样的API。

 import urllib2 import OpenSSL import struct def authenticate_game_center_user(gc_public_key_url, app_bundle_id, gc_player_id, gc_timestamp, gc_salt, gc_unverified_signature): apple_cert = urllib2.urlopen(gc_public_key_url).read() gc_pkey_certificate = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_ASN1, apple_cert) payload = gc_player_id.encode('UTF-8') + app_bundle_id.encode('UTF-8') + struct.pack('>Q', int(gc_timestamp)) + gc_salt try: OpenSSL.crypto.verify(gc_pkey_certificate, gc_unverified_signature, payload, 'sha1') print 'Signature verification is done. Success!' except Exception as res: print res public_key_url = 'https://sandbox.gc.apple.com/public-key/gc-sb.cer' player_GC_ID = 'G:1870391344' timestamp = '1382621610281' your_app_bundle_id = 'com.myapp.bundle_id' with open('./salt.dat', 'rb') as f_salt: with open('./signature.dat', 'rb') as f_sign: authenticate_game_center_user(public_key_url, your_app_bundle_id, player_GC_ID, timestamp, f_salt.read(), f_sign.read()) 

添加Python的答案,但使用PyCrypto 2.6(这是Google App Engine解决scheme)。 另外请注意,下载后的公共证书validation在这里没有完成,类似于上面使用OpenSSL的python回答。 无论如何,这一步真的有必要吗? 如果我们检查公钥URL是否是一个苹果域名,并且使用的是ssl(https),那么这是不是意味着它不受中间人攻击的保护?

无论如何,这是代码。 注意在连接和使用之前,二进制文本被重新转换为二进制文件。 此外,我不得不更新我的本地python安装使用PyCrypto 2.6之前,这将工作:

 from Crypto.PublicKey import RSA from Crypto.Signature import PKCS1_v1_5 from Crypto.Hash import SHA from base64 import b64decode from Crypto.Util.asn1 import DerSequence from binascii import a2b_base64 import struct import urlparse def authenticate_game_center_user(gc_public_key_url, app_bundle_id, gc_player_id, gc_timestamp, gc_salt, gc_unverified_signature): apple_cert = urllib2.urlopen(gc_public_key_url).read() #Verify the url is https and is pointing to an apple domain. parts = urlparse.urlparse(gc_public_key_url) domainName = ".apple.com" domainLocation = len(parts[1]) - len(domainName) actualLocation = parts[1].find(domainName) if parts[0] != "https" or domainName not in parts[1] or domainLocation != actualLocation: logging.warning("Public Key Url is invalid.") raise Exception cert = DerSequence() cert.decode(apple_cert) tbsCertificate = DerSequence() tbsCertificate.decode(cert[0]) subjectPublicKeyInfo = tbsCertificate[6] rsakey = RSA.importKey(subjectPublicKeyInfo) verifier = PKCS1_v1_5.new(rsakey) payload = gc_player_id.encode('UTF-8') payload = payload + app_bundle_id.encode('UTF-8') payload = payload + struct.pack('>Q', int(gc_timestamp)) payload = payload + b64decode(gc_salt) digest = SHA.new(payload) if verifier.verify(digest, b64decode(gc_unverified_signature)): print "The signature is authentic." else: print "The signature is not authentic." 
 require 'base64' require 'httparty' module GameCenter include HTTParty # HHTTParty settings HTTPARTY_TIMEOUT = 10 def authenticate_game_center_user(gc_public_key_url, gc_player_id, gc_timestamp, gc_salt, gc_unverified_signature) # Get game center public key certificate gc_pkey_certificate = get_gc_public_key_certificate(gc_public_key_url) # Check public key certificate unless public_key_certificate_is_valid?(gc_pkey_certificate) do # Handle error end # Check SSL errors unless OpenSSL.errors.empty? do # Handle OpenSSL errors end # Payload building payload = build_payload(gc_player_id, gc_timestamp, gc_salt) # Test signature unless signature_is_valid?(gc_pkey_certificate, gc_unverified_signature, payload) do # Handle error end # Check SSL errors unless OpenSSL.errors.empty? do # Handle OpenSSL errors end # Return player ID gc_player_id end def build_payload(player_id, timestamp, salt) player_id.encode("UTF-8") + "com.myapp.bundle_id".encode("UTF-8") + [timestamp.to_i].pack("Q>") + salt end private def get_gc_public_key_certificate(url) cert = HTTParty.get(url, timeout: HTTPARTY_TIMEOUT, debug_output: Rails.env.production?) OpenSSL::X509::Certificate.new(cert) rescue SocketError => e puts "Key error: " + e.inspect.to_s end def get_ca_certificate OpenSSL::X509::Certificate.new(File.read('./certs/apple/verisign_class_3_code_signing_2010_ca.cer')) end def public_key_certificate_is_valid?(pkey_cert) pkey_cert.verify(get_ca_certificate.public_key) end def signature_is_valid?(pkey_cert, signature, payload) pkey_cert.public_key.verify(OpenSSL::Digest::SHA1.new, signature, payload) end end 

这是我的ruby实现(作为模块)。 感谢你的Objective-C,它变得更容易了。

请注意,由于公钥证书尚未被苹果公司授权,苹果公司也没有提供任何CA证书来validation沙盒游戏中心证书,所以我被迫在第三方SSL服务网站上下载了CA证书。

我没有在生产中testing过,但在沙箱模式下工作正常。

我花了很多时间在PHP中实现它。 现在我想分享我的结果。

文档

您可以在Applefind一个非常简单的文档: https : //developer.apple.com/library/ios/documentation/GameKit/Reference/GKLocalPlayer_Ref/index.html#//apple_ref/occ/instm/GKLocalPlayer/generateIdentityVerificationSignatureWithCompletionHandler

[…]

  1. 使用第三方服务器上的publicKeyURL来下载公钥。
  2. 使用适当的签名机构validation公钥是由Apple签署的。
  3. 检索玩家的playerID和bundleID。
  4. 以列出的顺序将以下信息连接到数据缓冲区中:
    • playerID参数为UTF-8格式
    • 采用UTF-8格式的bundleID参数
    • Big-Endian UInt-64格式的时间戳参数
    • 盐参数
  5. 为缓冲区生成一个SHA-256哈希值。
  6. 使用步骤3中下载的公钥,validation步骤7中生成的哈希值是否与API提供的签名参数匹配。

注意! 7号是PHP中的陷阱,花费我几个小时。 您只需将原始连接的string传递给openssl_verify()函数。

从问题20147月9日更新如何使用PHPvalidation我的“第三方服务器”上的GKLocalPlayer? 帮我find了问题。

最终来源

 <?php // signature, publicKeyUrl, timestamp and salt are included in the base64/json data you will receive by calling generateIdentityVerificationSignatureWithCompletionHandler. $timestamp = $params["timestamp"]; // eg 1447754520194 $user_id = $params["user_id"]; // eg G:20010412315 $bundle_id = "com.example.test"; $public_key_url = $params["publicKeyUrl"]; // eg https://static.gc.apple.com/public-key/gc-prod-2.cer $salt = base64_decode($params["salt"]); // Binary $signature = base64_decode($params["signature"]); // Binary // Timestamp is unsigned 64-bit integer big endian $highMap = 0xffffffff00000000; $lowMap = 0x00000000ffffffff; $higher = ($timestamp & $highMap) >>32; $lower = $timestamp & $lowMap; $timestamp = pack('NN', $higher, $lower); // Concatenate the string $data = $user_id . $bundle_id . $timestamp . $salt; // ATTENTION!!! Do not hash it! $data = hash("sha256", $packed); // Fetch the certificate. This is dirty because it is neither cached nor verified that the url belongs to Apple. $ssl_certificate = file_get_contents($public_key_url); $pem = chunk_split(base64_encode($ssl_certificate), 64, "\n"); $pem = "-----BEGIN CERTIFICATE-----\n" . $pem . "-----END CERTIFICATE-----\n"; // it is also possible to pass the $pem string directly to openssl_verify if (($pubkey_id = openssl_pkey_get_public($pem)) === false) { echo "invalid public key\n"; exit; } // Verify that the signature is correct for $data $verify_result = openssl_verify($data, $signature, $pubkey_id, OPENSSL_ALGO_SHA256); openssl_free_key($pubkey_id); switch($verify_result) { case 1: echo "Signature is ok.\n"; break; case 0: echo "Signature is wrong.\n"; break; default: echo "An error occurred.\n"; break; } 

感谢代码示例,golang解决scheme:

 func DownloadCert(url string) []byte { b, err := inet.HTTPGet(url) if err != nil { log.Printf("http request error %s", err) return nil } return b } func VerifySig(sSig, sGcId, sBundleId, sSalt, sTimeStamp string, cert []byte) (err error) { sig, err := base64.StdEncoding.DecodeString(sSig) if err != nil { return } salt, err := base64.StdEncoding.DecodeString(sSalt) if err != nil { return } timeStamp, err := strconv.ParseUint(sTimeStamp, 10, 64) if err != nil { return } payload := new(bytes.Buffer) payload.WriteString(sGcId) payload.WriteString(sBundleId) binary.Write(payload, binary.BigEndian, timeStamp) payload.Write(salt) return verifyRsa(cert, sig, payload.Bytes()) } func verifyRsa(key, sig, content []byte) error { cert, err := x509.ParseCertificate(key) if err != nil { log.Printf("parse cert error %s", err) return err } pub := cert.PublicKey.(*rsa.PublicKey) h := sha256.New() h.Write(content) digest := h.Sum(nil) err = rsa.VerifyPKCS1v15(pub, crypto.SHA256, digest, sig) return err } 

一个小帮手

 func HTTPGet(fullUrl string) (content []byte, err error) { log.Printf("http get url %s", fullUrl) resp, err := http.Get(fullUrl) if err != nil { log.Printf("url can not be reached %s,%s", fullUrl, err) return } if resp.StatusCode != http.StatusOK { return nil, errors.New("ERROR_STATUS_NOT_OK") } body := resp.Body content, err = ioutil.ReadAll(body) if err != nil { log.Printf("url read error %s, %s", fullUrl, err) return } body.Close() return } 

testing代码

 func TestVerifyFull(t *testing.T) { cert := DownloadCert("https://sandbox.gc.apple.com/public-key/gc-sb-2.cer") if cert == nil { log.Printf("cert download error ") } sig := "sig as base64" salt := "salt as base64" timeStamp := "1442816155502" gcId := "G:12345678" bId := "com.xxxx.xxxx" err := VerifySig(sig, gcId, bId, salt, timeStamp, cert) log.Printf("result %v", err) } 

一个小function来validation证书下载url。 防止从任何地方下载任何东西

 func IsValidCertUrl(fullUrl string) bool { //https://sandbox.gc.apple.com/public-key/gc-sb-2.cer uri, err := url.Parse(fullUrl) if err != nil { log.Printf("not a valid url %s", fullUrl) return false } if !strings.HasSuffix(uri.Host, "apple.com") { log.Printf("not a valid host %s", fullUrl) return false } if path.Ext(fullUrl) != ".cer" { log.Printf("not a valid ext %s, %s", fullUrl, path.Ext(fullUrl)) return false } return true } 

感谢那些以其他语言提供解决scheme的人。

以下是Scala中相关的解决scheme(转换为Java的简单方法):

 private def verify( signatureAlgorithm: String, publicKey: PublicKey, message: Array[Byte], signature: Array[Byte]): Boolean = { val sha1Signature = Signature.getInstance(signatureAlgorithm) sha1Signature.initVerify(publicKey) sha1Signature.update(message) sha1Signature.verify(signature) } val x509Cert = Try(certificateFactory.generateCertificate(new ByteArrayInputStream(publicKeyBytes)).asInstanceOf[X509Certificate]) x509Cert.foreach { cert => signatureAlgorithm = Some(cert.getSigAlgName) } x509Cert.map(_.getPublicKey) match { case Success(pk) => log.debug("downloaded public key successfully") publicKey = Some(pk) } val buffer = r.id.getBytes("UTF-8") ++ bundleId.getBytes("UTF-8") ++ ByteBuffer.allocate(8).putLong(r.timestamp).array() ++ Base64.decode(r.salt) val result = verify(signatureAlgorithm.getOrElse("SHA256withRSA"), pk, buffer, Base64.decode(r.signature)) log.info("verification result {} for request {}", result, r) 

其中r是以下实例:

 case class IOSIdentityVerificationRequest( id: PlayerIdentity, // String publicKeyURL: String, signature: String, // base64 encoded bytes salt: String, // base64 encoded bytes timestamp: Long, error: Option[String]) extends IdentityVerificationRequest 

这是一个更新和改进的Ruby版本。 我已经用苹果沙箱testing了它,但是还没有进行生产。 我还logging了在哪里获得CA证书,以validation您从公钥URL获得的证书。

 # iOS Game Center verifier for 3rd party game servers written in Ruby. # # *** Credits *** # Based off of code and comments at https://stackoverflow.com/questions/17408729/how-to-authenticate-the-gklocalplayer-on-my-third-party-server # # *** Improvements *** # This version uses Ruby's built in HTTP client instead of a 3rd party gem. # It's updated to use SHA256 instead of SHA1. # It Base64 decodes the salt and signature. If your client or server already does this then you will need to remove the calls to Base64.decode64(). # It validates that the public key URL is from apple.com. # It has been tested with Apple's Game Center's sandbox public key URL (https://sandbox.gc.apple.com/public-key/gc-sb-2.cer) and works as of June 24th, 2015. # # *** Notes on public key certificate validation *** # You will need the correct code signing CA to verify the certificate returned from the pubic key URL. # You can download/verify the CA certificate here: https://knowledge.symantec.com/support/code-signing-support/index?page=content&actp=CROSSLINK&id=AR2170 # I have embedded the CA certificate for convenience so that you don't need to save it to your filesystem. # When the public key URL changes in the future, you may need to update the text in the ca_certificate_text() method. # # *** Usage *** # verified, reason = GameCenterVerifier.verify(...) class GameCenterVerifier # Verify that user provided Game Center data is valid. # False will be returned along with a reason if any validations fail. # Otherwise, it will return true and a nil reason if all validations pass. def self.verify(game_center_id, public_key_url, timestamp, salt, signature, bundle_id) salt = Base64.decode64(salt) signature = Base64.decode64(signature) payload = game_center_id.encode('UTF-8') + bundle_id.encode('UTF-8') + [timestamp.to_i].pack('Q>') + salt pkey_certificate = get_public_key_certificate(public_key_url) return false, 'Invalid public key url' unless public_key_url_is_valid?(public_key_url) return false, 'Invalid public key certificate' unless public_key_certificate_is_valid?(pkey_certificate) return false, 'OpenSSL errors (before signature check)' unless OpenSSL.errors.empty? return false, 'Invalid signature' unless signature_is_valid?(pkey_certificate, signature, payload) return false, 'OpenSSL errors (after signature check)' unless OpenSSL.errors.empty? return true, nil end private def self.get_public_key_certificate(url) uri = URI.parse(url) http = Net::HTTP.new(uri.host, uri.port) request = Net::HTTP::Get.new(uri.request_uri) http.use_ssl = true http.open_timeout = 5 http.read_timeout = 5 cert = http.request(request).body OpenSSL::X509::Certificate.new(cert) end def self.public_key_url_is_valid?(public_key_url) uri = URI(public_key_url) tokens = uri.host.split('.') return false if uri.scheme != 'https' return false if tokens[-1] != 'com' || tokens[-2] != 'apple' true end def self.public_key_certificate_is_valid?(pkey_cert) pkey_cert.verify(get_ca_certificate.public_key) end def self.signature_is_valid?(pkey_cert, signature, payload) pkey_cert.public_key.verify(OpenSSL::Digest::SHA256.new, signature, payload) end def self.get_ca_certificate OpenSSL::X509::Certificate.new(ca_certificate_text) end def self.ca_certificate_text data = <<EOF -----BEGIN CERTIFICATE----- MIIFWTCCBEGgAwIBAgIQPXjX+XZJYLJhffTwHsqGKjANBgkqhkiG9w0BAQsFADCB yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0 aG9yaXR5IC0gRzUwHhcNMTMxMjEwMDAwMDAwWhcNMjMxMjA5MjM1OTU5WjB/MQsw CQYDVQQGEwJVUzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xHzAdBgNV BAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxMDAuBgNVBAMTJ1N5bWFudGVjIENs YXNzIDMgU0hBMjU2IENvZGUgU2lnbmluZyBDQTCCASIwDQYJKoZIhvcNAQEBBQAD ggEPADCCAQoCggEBAJeDHgAWryyx0gjE12iTUWAecfbiR7TbWE0jYmq0v1obUfej DRh3aLvYNqsvIVDanvPnXydOC8KXyAlwk6naXA1OpA2RoLTsFM6RclQuzqPbROlS Gz9BPMpK5KrA6DmrU8wh0MzPf5vmwsxYaoIV7j02zxzFlwckjvF7vjEtPW7ctZlC n0thlV8ccO4XfduL5WGJeMdoG68ReBqYrsRVR1PZszLWoQ5GQMWXkorRU6eZW4U1 V9Pqk2JhIArHMHckEU1ig7a6e2iCMe5lyt/51Y2yNdyMK29qclxghJzyDJRewFZS AEjM0/ilfd4v1xPkOKiE1Ua4E4bCG53qWjjdm9sCAwEAAaOCAYMwggF/MC8GCCsG AQUFBwEBBCMwITAfBggrBgEFBQcwAYYTaHR0cDovL3MyLnN5bWNiLmNvbTASBgNV HRMBAf8ECDAGAQH/AgEAMGwGA1UdIARlMGMwYQYLYIZIAYb4RQEHFwMwUjAmBggr BgEFBQcCARYaaHR0cDovL3d3dy5zeW1hdXRoLmNvbS9jcHMwKAYIKwYBBQUHAgIw HBoaaHR0cDovL3d3dy5zeW1hdXRoLmNvbS9ycGEwMAYDVR0fBCkwJzAloCOgIYYf aHR0cDovL3MxLnN5bWNiLmNvbS9wY2EzLWc1LmNybDAdBgNVHSUEFjAUBggrBgEF BQcDAgYIKwYBBQUHAwMwDgYDVR0PAQH/BAQDAgEGMCkGA1UdEQQiMCCkHjAcMRow GAYDVQQDExFTeW1hbnRlY1BLSS0xLTU2NzAdBgNVHQ4EFgQUljtT8Hkzl699g+8u K8zKt4YecmYwHwYDVR0jBBgwFoAUf9Nlp8Ld7LvwMAnzQzn6Aq8zMTMwDQYJKoZI hvcNAQELBQADggEBABOFGh5pqTf3oL2kr34dYVP+nYxeDKZ1HngXI9397BoDVTn7 cZXHZVqnjjDSRFph23Bv2iEFwi5zuknx0ZP+XcnNXgPgiZ4/dB7X9ziLqdbPuzUv M1ioklbRyE07guZ5hBb8KLCxR/Mdoj7uh9mmf6RWpT+thC4p3ny8qKqjPQQB6rqT og5QIikXTIfkOhFf1qQliZsFay+0yQFMJ3sLrBkFIqBgFT/ayftNTI/7cmd3/SeU x7o1DohJ/o39KK9KEr0Ns5cF3kQMFfo2KwPcwVAB8aERXRTl4r0nS1S+K4ReD6bD dAUK75fDiSKxH3fzvc1D1PFMqT+1i4SvZPLQFCE= -----END CERTIFICATE----- EOF end end 

这是我在Elixir中的实现。

 def verify_login(player_id, public_key_url, timestamp, salt64, signature64, bundle_id) do salt = Base.decode64!(salt64) pay_load = <<player_id :: binary, bundle_id :: binary, timestamp :: big-size(64), salt :: binary>> pkey_cert = get_public_key_certificate(public_key_url) cert = :public_key.pkix_decode_cert(pkey_cert, :otp) case cert do {:OTPCertificate, {:OTPTBSCertificate, _, _, _, _, _, _, {:OTPSubjectPublicKeyInfo, _, key}, _, _, _}, _, _} -> signature = Base.decode64!(signature64) case :public_key.verify(pay_load, :sha256, signature, key) do true -> :ok false -> {:error, "apple login verify failed"} end end end def get_public_key_certificate(url) do case HTTPoison.get(url) do {:ok, %HTTPoison.Response{body: body}} -> body end end