I want to use RSA public key encryption to encrypt something in Java in an Android app using a public key and then to decrypt this in a web service in PHP. As with most of these tasks, there are so many variations, file formats, missing functionality and whatever in each language that these tasks can cause muchos confusion.

Anyway, I managed it, thanks partly to this article from 2009. Some basic information, I have a public key in DER format, which is the binary format and not the Base64 encoded format. The code to utilise this in Java/Android, from the blog article is like this:

private String PublicKeyEncrypt(byte[] data)
{
PublicKey pk = null;
try
{
InputStream f = getAssets().open("publickey.der");
DataInputStream dis = new DataInputStream(f);
byte[] keyBytes;
keyBytes = new byte[(int)f.available()];
dis.readFully(keyBytes);
dis.close();

X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
pk = kf.generatePublic(spec);
}
catch (Exception e) {
e.printStackTrace();
}

final byte[] cipherText = encrypt(data, pk);
return Base64.encodeToString(cipherText,Base64.DEFAULT);
}

private static byte[] encrypt(byte[] data, PublicKey key)
{
byte[] cipherText = null;
try {
final Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
cipherText = cipher.doFinal(data);
}
catch (Exception e) {
e.printStackTrace();
}
return cipherText;
}

Note that getAssets() is just how I obtain the location of the embedded public key in Android. Also note that I use the RSA/ECB/PKCS1Padding. There are variations but this is the most standard of these. You could combine these two methods into one, I just separated them up to make them more readable.

The much simpler decryption using a .pem base64 encoded private key in PHP looks like this:

private function DecryptAndValidateData($data)
{
try
{
if (openssl_private_decrypt(base64_decode($data), $decrypted, file_get_contents(Yii::getPathOfAlias( 'webroot' ) . '/privatekey.pem')))
{
// Do something with $decrypted
}
catch(CException $e)
{
$this->sendResponse(500, 'Internal Server Error');
}
}
}

Note that I base64 encoded/decoded the encrypted data since it is sent across a web channel and I didn't want any of the data to be lost or corrupted along any non-ascii safe connections. The Yii stuff is because I am using the Yii framework so obviously the obtaining of the private key file path might be different for you.

Easy as that! I started with the .Net version but reading key files is a bit of a pain in .net for some reason and the BouncyCastle library download site is currently broken!

EDIT
If you would like to use OAEP padding instead of PKCS1 (standard) padding, which apparently is a good idea, you can use RSA/ECB/OAEPWithSHA-1AndMGF1Padding in the Java call to Cipher.getInstance and pass the flag OPENSSL_PKCS1_OAEP_PADDING to openssl_private_decrypt as the third parameter in PHP.