How to authenticate the GKLocalPlayer on my 'third party server'?
iOS7引入了新的GKLocalPlayer方法
有人知道如何永久使用它吗?
我假设苹果服务器端会有一些公共 API ..
这是一个 C# WebApi 服务器端版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | 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; } } |
以下是使用objective C 进行身份验证的方法。如果你需要它在另一种语言应该是微不足道的翻译。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | -(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:×tampBE 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 日起,Apple 现在在证书上使用 SHA256 而不是 SHA1。 https://devforums.apple.com/thread/263789?tstart=0
我花了很多时间在 PHP 中实现它。现在我想分享我的结果。
文档
您可以在 Apple 找到一个非常简单的文档:https://developer.apple.com/library/ios/documentation/GameKit/Reference/GKLocalPlayer_Ref/index.html#//apple_ref/occ/instm/GKLocalPlayer/generateIdentityVerificationSignatureWithCompletionHandler
[...]
- UTF-8 格式的 playerID 参数
- UTF-8 格式的 bundleID 参数
- Big-Endian UInt-64 格式的时间戳参数
- salt参数
注意!数字 7 是 PHP 中的一个陷阱,花了我几个小时。您只需将原始连接字符串传递给 openssl_verify() 函数。
2014 年 7 月 9 日更新的问题如何使用 PHP 在我的"第三方服务器"上验证 GKLocalPlayer?帮我找到了问题。
最终来源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | <?php // signature, publicKeyUrl, timestamp and salt are included in the base64/json data you will receive by calling generateIdentityVerificationSignatureWithCompletionHandler. $timestamp = $params["timestamp"]; // e.g. 1447754520194 $user_id = $params["user_id"]; // e.g. G:20010412315 $bundle_id ="com.example.test"; $public_key_url = $params["publicKeyUrl"]; // e.g. 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,"\ "); $pem ="-----BEGIN CERTIFICATE-----\ " . $pem ."-----END CERTIFICATE-----\ "; // 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\ "; 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.\ "; break; case 0: echo"Signature is wrong.\ "; break; default: echo"An error occurred.\ "; break; } |
添加 Python 的答案,但使用 PyCrypto 2.6(这是 Google App Engine 解决方案)。
另请注意,此处未完成下载后对公共证书的验证,类似于上面使用 OpenSSL 的 python 答案。这一步真的有必要吗?如果我们检查公钥 URL 是否要访问苹果域并且它使用 ssl (https),??这是否意味着它受到保护免受中间人攻击?
不管怎样,这里是代码。请注意,二进制文本在连接和使用之前会重新转换为二进制。此外,我必须更新我的本地 python 安装以使用 PyCrypto 2.6,然后才能使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | 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." |
谢谢,@odyth。谢谢,@Lionel。
我想在这里添加 Python 版本(基于你的)。它有一个小缺陷 - Apple 证书未经验证 - 在 pyOpenSSL 绑定中没有这样的 API。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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()) |
感谢代码示例,golang 解决方案来了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | 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 } |
一个小小的http助手
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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 } |
测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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) } |
验证证书下载 url 的小功能。防止从任何地方下载任何东西
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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 } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | 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,它变得容易多了。
请注意,我在第三方 ssl 服务网站上被迫下载 CA 证书,因为公钥证书尚未签署 Apple 并且 Apple 未提供任何 CA 证书来验证沙盒游戏中心到目前为止的证书。
我还没有在生产中测试过这个,但是在沙盒模式下它可以正常工作。
这是我在 Elixir 中的实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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 |
这是一个更新和改进的 Ruby 版本。我已经使用 Apple 沙箱对其进行了测试,但还没有进行生产。我还记录了从哪里获取 CA 证书,以验证您从公钥 URL 收到的证书。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | # 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 |
感谢那些提供其他语言解决方案的人。
以下是 Scala 中的相关解决方案(转换为 Java 很简单):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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 是一个实例:
1 2 3 4 5 6 7 | 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 |