Friday, 3 January 2014

RSA Public Key Encryption between .Net, PHP and Java

Introduction

I have the unenviable task of communicating between a Java Android app, via a PHP web service to a .Net web service. This sounds a bit overkill but although I like PHP for simple REST APIs, it cannot handle a specific type of encryption that I decided to use in my database so I have to delegate to a .Net web service instead.

Anyway......one of the things I have been using recently is public/private key encryption to secure communications between the Android app and our web service to prove that the server has been involved with the handshake. One of the difficulties I had was trying to get the various flavours and permutations of encryption to work between the different platforms and languages. One of the major problems with encryption is that often if it doesn't work, you don't get many clues as to what is wrong.

Public Key Encrypt in Java, private key decrypt in PHP


I have already covered this before although I can't find the blog post! The functionality is built into Java so it is fairly straight-forward. Naturally you need a key pair, mine are just plain RSA 4096 bits long exported as .der format (not pem) created using openssl on the command line:

// Get the public key into the byte array keyBytes. Mine is read in directly from a .der file using a FileInputStream
// data can either be raw binary or in my case I call getBytes() on a string
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
PublicKey pk = kf.generatePublic(spec);
final Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, pk);
cipherText = cipher.doFinal(data);
// Optionally base64 encode the cipherText (which I do)

In my case I then send this data to a web service which then uses the private key to decrypt the data I have encrypted.

$key = file_get_contents('path/to/privatekey.pem');
openssl_private_decrypt(base64_decode($data), $decrypted, $key, OPENSSL_PKCS1_OAEP_PADDING)

As you might imagine, data is just the base64 encoded data from the Java app and $decrypted is an output parameter which is filled with the decrypted data. openssl_private_decrypt is a boolean function so you can test whether the decryption worked or not.

Public Key Encrypt in .Net, private key decrypt in Java

 In another scenario, I pass a public key to the .Net web service from the Android app so that the web service can encrypt some data that only the Android app can decrypt. In the .Net web service, I use a .Net port of the OWASP ESAPI security library but I extended it to provide public key encryption/decryption, something not included by default. To ,make this work correctly, however, I ended up having to use the .Net version of the Bouncy Castle encryption library. This meant the code ended up like this:

// public key is a Stream in this case but you can convert a byte[] to a stream using "new MemoryStream(publickey, false)"
// Some of the code is simply to convert between the MS representation of a key and the Bouncy Castle format
var rsaKeyParameters = (RsaKeyParameters)PublicKeyFactory.CreateKey(publickey);
var rsaParameters = new RSAParameters();
rsaParameters.Modulus = rsaKeyParameters.Modulus.ToByteArrayUnsigned();
rsaParameters.Exponent = rsaKeyParameters.Exponent.ToByteArrayUnsigned();
var rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(rsaParameters);
return Convert.ToBase64String(rsa.Encrypt(Encoding.UTF8.GetBytes(plaintext), true));

Naturally, you need appropriate error checking etc. I haven't included it here to keep the examples tidy. This then gets returned ultimately to the Java app which has to decrypt it using its private key. The example is very similar to the first code listing above but it needs to use a different Key Specification and decrypt mode instead of encrypt mode (really?!).

PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey pk = kf.generatePrivate(spec);
final Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
cipher.init(Cipher.DECRYPT_MODE, key);
cipherText = cipher.doFinal(data);

You need to be careful about what is base64 encoded and what is in raw byte form (and add the converters in where needed). This is more confusing when your raw data is actually a string since it would be converted into bytes before encryption and encoding into a different string afterwards!
Post a Comment