title: How to authenticate GKLocalPlayer on my third-party-server using Java JDK 1.7?
body: <p>Using the Apple's Game Center authentication verification steps <a href="https://developer.apple.com/library/ios/documentation/GameKit/Reference/GKLocalPlayer_Ref/Reference/Reference.html#//apple_ref/occ/instm/GKLocalPlayer/generateIdentityVerificationSignatureWithCompletionHandler:" rel="nofollow noreferrer">outlined here</a>, the verification logic below has been implemented using Java. However, this always fails.</p>
  
  <pre><code>import java.net.URL;
  
  import java.nio.ByteBuffer;
  
  import java.nio.ByteOrder;
  
  import java.security.KeyPair;
  
  import java.security.KeyPairGenerator;
  
  import java.security.MessageDigest;
  
  import java.security.PrivateKey;
  
  import java.security.PublicKey;
  
  import java.security.SecureRandom;
  
  import java.security.Signature;
  
  import java.security.cert.Certificate;
  
  import java.security.cert.CertificateFactory;
  
  import java.security.spec.AlgorithmParameterSpec;
  
  import java.util.Arrays;
  
  import javax.crypto.Cipher;
  
  import javax.xml.bind.DatatypeConverter;
  
  public class Verifier {
  
      public static void main(String[] args) {
  
          verify1();   
  
      }
  
  
  
      public static void verify1() {
  
          try {
  
              byte[] playerID = "G:90082947".getBytes("UTF-8");
  
              byte[] bundleID = "com.appledts.GameCenterSamples".getBytes("UTF-8");
  
  
  
              long ts = 1392078336714L;
  
              final ByteBuffer tsByteBuffer = ByteBuffer.allocate(8);
  
              tsByteBuffer.order(ByteOrder.BIG_ENDIAN);
  
              tsByteBuffer.putLong(ts);           
  
              byte[] timestamp = tsByteBuffer.array();
  
  
  
              byte[] salt = DatatypeConverter.parseBase64Binary("xmvbZQ==");
  
  
  
              byte[] sigToCheck = DatatypeConverter.parseBase64Binary("AmyNbm+7wJOjXv6GXI/vAEcl6gSX1AKxPr3GeExSYCiaxVaAeIvC23TWtp1/Vd/szfq1r1OzwrvkHeSSiskWMsMXaGQWUmiGtCnf9fqBU75T5PwNLCj4H9Nd5QENCMV/CFgVyGEi4X6Wlp18kqJPk/ooS6jLJwcWIe6DyrR1bQHl6YzKTfB4ACl2JEccBDz8dArKTrh4vFcQF4a+DtERm283Y2ue1DwG8lqWrYhsRO5v7vrW3lVpn5t25QXc+Y35zJ/il+lZJxKAgASwrKaq3G8RStdkeXCER23fSYhTmbLFqkFRWnmzu38hmLt5/iivUbm8NgELXP0SyQoYLMvfmA==");
  
  
  
              ByteBuffer dataBuffer = ByteBuffer.allocate(playerID.length+bundleID.length+8+salt.length)
  
                  .put(playerID)
  
                  .put(bundleID)
  
                  .put(timestamp)
  
                  .put(salt);
  
  
  
  
  
              Certificate cert = CertificateFactory.getInstance("X.509")
  
                      .generateCertificate(new URL("https://sandbox.gc.apple.com/public-key/gc-sb.cer").openConnection().getInputStream());
  
  
  
  
  
              Signature sig = Signature.getInstance("SHA1withRSA");
  
              sig.initVerify(cert);
  
  
  
              sig.update(dataBuffer);
  
  
  
              final boolean verify = sig.verify(sigToCheck);
  
              System.out.println("signature verifies: " + verify);                            
  
  
  
          } catch (Exception e) {            
  
              e.printStackTrace();
  
          }
  
      }        
  
  }
  </code></pre>
  
  <p>There were no loss of bits in transferring data from the iOS 7 client to the server. This was verified by writing the binary bits to a file both from xCode and Java, generating their hex, and seeing if there were any diffs (note, the diffs just show the file name diffs):</p>
  
  <pre><code>$ xxd -i salt_Java.txt salt_java.xxd
  
  $ xxd -i salt_xcode.txt salt_xcode.xxd
  
  $ xxd -i sigToCheck_Java.txt sigToCheck_java.xxd
  
  $ xxd -i sigToCheck_xcode.txt sigToCheck_xcode.xxd
  
  $ diff salt_java.xxd salt_xcode.xxd 
  
  1c1
  
  &lt; unsigned char salt_Java_txt[] = {
  
  ---
  
  &gt; unsigned char salt_xcode_txt[] = {
  
  4c4
  
  &lt; unsigned int salt_Java_txt_len = 4;
  
  ---
  
  &gt; unsigned int salt_xcode_txt_len = 4;
  
  $ diff sigToCheck_java.xxd sigToCheck_xcode.xxd 
  
  1c1
  
  &lt; unsigned char sigToCheck_Java_txt[] = {
  
  ---
  
  &gt; unsigned char sigToCheck_xcode_txt[] = {
  
  25c25
  
  &lt; unsigned int sigToCheck_Java_txt_len = 256;
  
  ---
  
  &gt; unsigned int sigToCheck_xcode_txt_len = 256;
  
  $ 
  </code></pre>
  
  <p>I believe this fails because of the underlying Java libraries that <a href="http://docs.oracle.com/javase/7/docs/api/java/security/Signature.html" rel="nofollow noreferrer">Signature class</a> uses, since the Objective-C <a href="https://stackoverflow.com/questions/17408729/how-to-authenticate-the-gklocalplayer-on-my-third-party-server">solution listed here</a> appears to successfully verify the same credentials.</p>
  
  <p>My next attempt was to use the Java's [Cipher] and [MessageDigest] libraries instead of the [Signature] library, but this too fails. I suspect there are other steps missing before the signature digest bits can be checked with the provided signature bits.</p>
  
  <pre><code>final MessageDigest md = MessageDigest.getInstance("SHA1");
  
  byte[] digest = md.digest(dataBuffer.array());
  
  // RSA decrypt
  
  Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
  
  cipher.init(Cipher.DECRYPT_MODE, cert);
  
  byte[] decrypted = cipher.doFinal(sigToCheck);    
  
  
  
  System.out.println("signature verifies: " + Arrays.equals(digest, decrypted));
  </code></pre>
  
  <p>Are there alternatives to verifying the digital signature or any gaps in the solutions posted above?</p>