title: RSA: encrypt in iOS, decrypt in Java
body: <p>I have a public key that's sent from a Java server. The base64 encoded strings match before I decode and strip out the ASN.1 headers. I store the public key in the keychain with <code>SecItemAdd</code>.</p>
  
  <p>So I'm trying to encrypt the data using the public key and decrypt it with the private key in Java. I'm using <code>SecKeyEncrypt</code> on the iOS side and <code>Cipher</code> on the Java side.</p>
  
  <p>What I'm encrypting is the symmetric AES key that encrypts my actual data, so the key length is 16 bytes. When simply base64 encoding the key, everything works, so I know something is wrong with this RSA encryption.</p>
  
  <p>Here's an example of my iOS call:</p>
  
  <pre><code>OSStatus sanityCheck = SecKeyEncrypt(publicKey,
          kSecPaddingPKCS1,
          (const uint8_t *) [incomingData bytes],
          keyBufferSize,
          cipherBuffer,
          &amp;cipherBufferSize
  );
  </code></pre>
  
  <p>Here's an example of my Java call:</p>
  
  <pre><code>public static byte[] decryptMessage (byte[] message, PrivateKey privateKey, String algorithm) {
      if (message == null || privateKey == null) {
          return null;
      }
      Cipher cipher = createCipher(Cipher.DECRYPT_MODE, privateKey, algorithm, false);
      if (cipher == null) {
          return null;
      }
  
      try {
          return cipher.doFinal(message);
      }
      catch (IllegalBlockSizeException e) {
          e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
          return null;
      }
      catch (BadPaddingException e) {
          e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
          return null;
      }
  }
  
  private static Cipher createCipher (int mode, Key encryptionKey, String algorithm, boolean useBouncyCastle) {
      Cipher cipher;
  
      try {
          if (useBouncyCastle) {
              Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
              cipher = Cipher.getInstance(algorithm, "BC");
          }
          else {
              cipher = Cipher.getInstance(algorithm);
          }
      }
      catch (NoSuchAlgorithmException e) {
          e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
          return null;
      }
      catch (NoSuchPaddingException e) {
          e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
          return null;
      }
      catch (NoSuchProviderException e) {
          e.printStackTrace();
          return null;
      }
  
      try {
          cipher.init(mode, encryptionKey);
      }
      catch (InvalidKeyException e) {
          e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
          return null;
      }
  
      return cipher;
  }
  </code></pre>
  
  <p>I've tried so many combinations and nothing has worked.</p>
  
  <ul>
  <li>iOS: PKCS1, Java: RSA/ECB/PKCS1Padding</li>
  <li>iOS: PKCS1, Java: RSA</li>
  <li>iOS: PKCS1, Java: RSA/None/PKCS1Padding (throws <code>org.bouncycastle.crypto.DataLengthException: input too large for RSA cipher.</code>)</li>
  <li>iOS: OAEP, Java: RSA/ECB/OAEPWithSHA-1AndMGF1Padding</li>
  <li>iOS: OAEP, Java: RSA/ECB/OAEPWithSHA-256AndMGF1Padding</li>
  </ul>
  
  <p>I've also tried using the internal Java provider as well as the BouncyCastle provider. The <code>javax.crypto.BadPaddingException</code> gets thrown every time, but the message is different for each combination. Some show <code>Data must start with zero</code>, while others are <code>Message is larger than modulus</code>.</p>
  
  <p>The <code>iOS: PKCS1, Java: RSA</code> doesn't throw an exception, but the resulting decrypted <code>byte[]</code> array should be length 16, but it's length 256, which means the padding isn't correctly stripped out.</p>
  
  <p>Can someone help?</p>
  
  <p>*** <strong>EDIT</strong> ***</p>
  
  <p>As I'm doing more testing, I came across this page (<a href="http://javadoc.iaik.tugraz.at/iaik_jce/current/iaik/pkcs/pkcs1/RSACipher.html" rel="noreferrer">http://javadoc.iaik.tugraz.at/iaik_jce/current/iaik/pkcs/pkcs1/RSACipher.html</a>), which essentially tells me that <code>RSA == RSA/None/PKCS1Padding</code>. The decryption works in the sense that there are no exceptions, but I'm still getting a decrypted key whose byte[] is length 256 instead of length 16.</p>
  
  <p>Another point of interest. It seems that if the Java server has the public key generated from the iOS device and encrypted using <code>Cipher.getInstance("RSA")</code>, the phone is able to decode the message correctly with RSA/PKCS1.</p>
  
  <p>*** <strong>EDIT 2</strong> ***</p>
  
  <p>I have looked at these tutorials and looked through my code again on the iOS side:</p>
  
  <ul>
  <li><a href="http://blog.flirble.org/2011/01/05/rsa-public-key-openssl-ios/" rel="noreferrer">http://blog.flirble.org/2011/01/05/rsa-public-key-openssl-ios/</a></li>
  <li><a href="http://blog.wingsofhermes.org/?p=42" rel="noreferrer">http://blog.wingsofhermes.org/?p=42</a></li>
  <li><a href="http://blog.wingsofhermes.org/?p=75" rel="noreferrer">http://blog.wingsofhermes.org/?p=75</a></li>
  </ul>
  
  <p>As far as I can tell, my code is doing everything correctly. One significant difference was in how I was saving the key, so I tried saving it the other way:</p>
  
  <pre><code>    OSStatus error = noErr;
      CFTypeRef persistPeer = NULL;
  
      NSMutableDictionary * keyAttr = [[NSMutableDictionary alloc] init];
  
      keyAttr[(__bridge id) kSecClass] = (__bridge id) kSecClassKey;
      keyAttr[(__bridge id) kSecAttrKeyType] = (__bridge id) kSecAttrKeyTypeRSA;
      keyAttr[(__bridge id) kSecAttrApplicationTag] = [secKeyWrapper getKeyTag:serverPublicKeyTag];
      keyAttr[(__bridge id) kSecValueData] = strippedServerPublicKey;
      keyAttr[(__bridge id) kSecReturnPersistentRef] = @YES;
  
      error = SecItemAdd((__bridge CFDictionaryRef) keyAttr, (CFTypeRef *)&amp;persistPeer);
  
      if (persistPeer == nil || ( error != noErr &amp;&amp; error != errSecDuplicateItem)) {
          NSLog(@"Problem adding public key to keychain");
          return;
      }
  
      CFRelease(persistPeer);
  </code></pre>
  
  <p>That save was successful, but the end result was the same: the decrypted AES key was still 256 bytes long instead of 16 bytes.</p>