Monday, April 27, 2015

RSA with C# and PHP

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 by default, but can convert between them.
Note: In the previous post I used external classes for the conversion, but then noticed, that it can also be done with phpseclib alone. The previous post thus is identical to this one except the conversion part, but I left it online for anyone who is interested in the used external classes. Further I believe, that the implementation for the conversion used there is faster (here phpseclib converts the PEM format to the XML format, in the old post C# does this - so someone wanting to convert many keys should do so in C#).

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.

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/ClientServerRSADirect.php):

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

session_start();

if (!isset($_SESSION["username"])) {
     $_SESSION["username"] = $_POST["username"];
     $_SESSION["ClientPubKey"] = $_POST["ClientPubKey"];
    
     $rsa = new Crypt_RSA();
     extract($rsa->createKey());
     $_SESSION["ServerPrivateKey"] = $privatekey;
    
     $rsa->setPublicKey($publickey);
     echo $rsa->getPublicKey(CRYPT_RSA_PUBLIC_FORMAT_XML);
}
else {
     $rsaclient = new Crypt_RSA();
     $rsaclient->loadKey($_SESSION["ClientPubKey"], CRYPT_RSA_PRIVATE_FORMAT_XML);
     echo base64_encode($rsaclient->encrypt("Hello client"))."<br />";
    
     $rsaserver = new Crypt_RSA();
     $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 by calling getPublicKey() and passing over XML as format. Here the previous call of setPublicKey() is important, as otherwise nothing is returned.
On the next call via loadKey() the saved public key of the client is imported in the XML format 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 ServerPubKey = HTTPPost("http://bloggeroliver.bplaced.net/PHPExamples/ClientServerRSADirect.php", "username=MyName&ClientPubKey=" + Uri.EscapeDataString(ClientRSA.ToXmlString(false)));

            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/ClientServerRSADirect.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 in the XML format. 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