Sunday, April 26, 2015

RSA with C# and PHP (with external classes)

Note: The next post is a simpler version of this one, the external classes for the key conversion are not needed there anymore, I recommend using the next post.

After I explained in previous posts seperately how to use RSA in C# and RSA in PHP, I today want to show how to use this combining both languages.
I am doing this, since the implementation is somewhat tricky, especially concerning the different used public / private key formats and the coding of the data when transmitting. Further I only found pieces of explanations about this on the internet, thus I wanted to post here a complete working example, in which PHP server and C# client both create keys, exchange them and send each other encrypted messages.

The .Net RSA implementation manages keys in an XML format, the in the previous post described PHP implementation phpseclib uses the PEM format. Although a conversation would of course be possible manually, I used for both directions external classes. To be able to read the XML format in PHP, I used this extension of the RSA class from phpseclib.
To read the PEM format with C#, I slightly changed the class OpenSSLKeys.cs and used that.

Read XML format in PHP: On the Codeblog linked above the idea was described and an extension of the RSA class posted. First one has to download the script (since the download link does not work on the site, here a reupload), which then has to be uploaded into the same directory as the file RSA.php from phpseclib. Then we include the script RSA_XML.php in the code via include and use the class Crypt_RSA_XML instead of Crypt_RSA. This is an extension of the class Crypt_RSA and provides next the standard function loadKey() also the function loadKeyfromXML(), with which a key can be read in which has been created in C# via the function ToXmlString() of the class RSACryptoServiceProvider.

Read PEM format in C#: For this I used the above linked class OpenSSLKeys.cs, which has been developed by JavaScience. This has to be added to the project, then we can access the needed function with JavaScience.opensslkey.DecodePEMKey(). I slightly changed the file, since in its original form it was thought of as a standalone program, with which one can communicate via the console. In principle I just removed the main function and added a return value to the function (only tested for converting public and private keys, the class can do more). The changed file can be downloaded here.

As already mentioned, one also has to be a bit careful when transmitting the data. I use the HTTP Post method for sending data to the server. First one has to get the used coding of the server, in my case it is UTF8. Thus I convert data into this format. When now sending a public key in the XML format, this contains amongst others symbols like <, >. These are viewed as control characters by the server, thus we have to escape them, which we do via Uri.EscapeDataString(). When encryting a string with RSA the output is pretty cryptic, a lot of special characters result. These cannot be represented in UTF8, therefor we convert the result via Convert.ToBase64String() into a Base64 string. The server then has to convert it back. We also escape this string to correctly send possible control characters. When outputting the public key on the server via echo, we also use the function nl2br(), which ensures the correct displaying of linebreaks.

Now let us come to the code, I want to present a little sample program. The C# program and the PHP script both create public / prviate key pairs and communicate the public key to the other. Then they encrypt a message with it, send it and the receiver decrypts it using its private key.
The PHP script looks as follows (http://bloggeroliver.bplaced.net/PHPExamples/ClientServerRSA.php):

<?php
include('Crypt/RSA_XML.php');

session_start();

if (!isset($_SESSION["username"])) {
     $_SESSION["username"] = $_POST["username"];
     $_SESSION["ClientPubKey"] = $_POST["ClientPubKey"];
    
     $rsa = new Crypt_RSA_XML();
     extract($rsa->createKey());
     $_SESSION["ServerPrivateKey"] = $privatekey;
     echo nl2br($publickey);
}
else {
     $rsaclient = new Crypt_RSA_XML();
     $rsaclient->loadKeyfromXML($_SESSION["ClientPubKey"]);
     echo base64_encode($rsaclient->encrypt("Hello client"))."<br />";
    
     $rsaserver = new Crypt_RSA_XML();
     $rsaserver->loadKey($_SESSION["ServerPrivateKey"]);
     echo "Client sent: ".$rsaserver->decrypt(base64_decode($_POST["SecretMessage"]));
}
?>

We work with sessions (see the post about sessions in C#). The script is called twice from the C# program. If no session has been created yet, the server first creates a keypair and saves its private key. Also it saves the sent public key of the client and outputs its own public one. On the next call via loadKeyFromXML() the saved public key of the client is imported and the text "Hello client" encrypted. For transmitting this is converted into a Base64 string and outputted. Further the server loads its private key and with that decrypts the message sent by the client, which also was transmitted as a Base64 string.

The C# code looks as follows:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

using System.Net;
using System.IO;
using System.Security.Cryptography;

namespace ClientServerRSA
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        CookieContainer Cookie = null;

        private void Form1_Load(object sender, EventArgs e)
        {
            Cookie = new CookieContainer();
            RSACryptoServiceProvider ClientRSA = new RSACryptoServiceProvider();
            string ServerPubKeyOutput = HTTPPost("http://bloggeroliver.bplaced.net/PHPExamples/ClientServerRSA.php", "username=MyName&ClientPubKey=" + Uri.EscapeDataString(ClientRSA.ToXmlString(false)));
            File.WriteAllText("serverpubkey.txt", ServerPubKeyOutput.Replace("<br />", ""));
            StreamReader sr = System.IO.File.OpenText("serverpubkey.txt");
            string pemkey = sr.ReadToEnd();
            string ServerPubKey = JavaScience.opensslkey.DecodePEMKey(pemkey);

            RSACryptoServiceProvider ServerRSA = new RSACryptoServiceProvider();
            ServerRSA.FromXmlString(ServerPubKey);
            string SecretMessage = Uri.EscapeDataString(Convert.ToBase64String(RSAEncrypt(Encoding.UTF8.GetBytes("Hello server"), ServerRSA.ExportParameters(false))));

            string ServerAnswer = HTTPPost("http://bloggeroliver.bplaced.net/PHPExamples/ClientServerRSA.php", "username=MyName&SecretMessage=" + SecretMessage);
            string[] SplitAnswer = ServerAnswer.Split(new string[] { "<br />" }, StringSplitOptions.None);
            string ServerMessage = Encoding.UTF8.GetString(RSADecrypt(Convert.FromBase64String(SplitAnswer[0]), ClientRSA.ExportParameters(true)));
            MessageBox.Show("Server sent: " + ServerMessage + Environment.NewLine + SplitAnswer[1]);
        }




        private string HTTPPost(string url, string postparams)
        {
            string responseString = "";

            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.CookieContainer = Cookie; // explicitely use the cookiecontainer to save the session

            string postData = postparams;
            byte[] data = Encoding.UTF8.GetBytes(postData);

            request.Method = "POST";
            request.ContentType = "application/x-www-form-urlencoded; charset=utf-8";
            request.ContentLength = data.Length;

            using (var stream = request.GetRequestStream())
            {
                stream.Write(data, 0, data.Length);
            }

            var response = (HttpWebResponse)request.GetResponse();

            responseString = new StreamReader(response.GetResponseStream()).ReadToEnd();

            return responseString;
        }

        static public byte[] RSAEncrypt(byte[] DataToEncrypt, RSAParameters RSAKeyInfo)
        {
            byte[] encryptedData;

            RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
            RSA.ImportParameters(RSAKeyInfo);

            encryptedData = RSA.Encrypt(DataToEncrypt, true);

            return encryptedData;
        }

        static public byte[] RSADecrypt(byte[] DataToDecrypt, RSAParameters RSAKeyInfo)
        {
            byte[] decryptedData;

            RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
            RSA.ImportParameters(RSAKeyInfo);

            decryptedData = RSA.Decrypt(DataToDecrypt, true);

            return decryptedData;
        }
    }
}

In the functions RSADecrypt() and RSAEncrypt() passing true as the second parameter is important, this enables the usage of OAEP padding (the default setting for phpseclib, but it can also be changed there).
Otherwise I explained the functions HTTPPost(), RSADecrypt() and RSAEncrypt() in previous posts, thus I will not mention them again here.
In the 3rd line of Form_Load() we wescape the created public key and send this to the server. Then we read its answer, its public key, and write it to a file. This PEM file we then convert via JavaScience.opensslkey.DecodePEMKey() into an XML key and import it. With this we encrypt the message "Hello server" and send this string encoded as a Base64 string and escaped to the server. Eventually we decrypt the message from the server.

No comments:

Post a Comment