Montag, 27. April 2015

RSA in C# und PHP

Nachdem ich in vorigen Posts getrennt beschrieben habe, wie man RSA in C# und RSA in PHP benutzt, möchte ich heute zeigen, wie man dies sprachübergreifend tut.
Ich tue dies, da die Umsetzung doch ein paar Tücken bereithält, insbesondere bezüglich des verwendeten Public / Private Key Formats und der Kodierung der Daten bei der Übertragung.
Außerdem habe ich im Internet nur stückweise Erklärungen gefunden, deswegen wollte ich hier ein komplett funktionsfähiges Beispiel posten, bei welchem PHP Server und C# Client beide Keys erzeugen, austauschen und verschlüsselte Daten hin und her senden.

Die .Net RSA Implementierung verwaltet Schlüssel in einem XML Format, die im vorigen Post beschriebene PHP Implementierung phpseclib verwendet standardmäßig das PEM Format, kann aber von und in das XML Format konvertieren.
Hinweis: Im vorigen Post habe ich hier noch externe Klassen für die Konvertierung verwendet, aber dann festgestellt, dass es auch mit Bordmitteln der phpseclib geht. Der vorigen Post ist bis auf diesen Teil identisch zu diesem, ich habe ihn allerdings online gelassen, falls jemand sich noch für die dort verwendeten externen Klassen interessiert. Außerdem denke ich, dass die dortige Implementierung zur Konvertierung der Formate wesentlich performanter ist (hier konvertiert phpseclib das PEM Formt in ein XML Format, im alten Post tut dies C# - wer also viele Keys konvertieren möchte, sollte dies besser mit C# tun).

Nun muss man noch ein bisschen bei der Datenübertragung mit HTTP Post bezüglich des Formats aufpassen. Ich verwende die HTTP Post Methode zur Übertragung von Daten an den Server. Zuerst muss man die verwendete Kodierung des Servers herausfinden, bei mir ist es UTF8. Deswegen werden die Daten im Post in das Format konvertiert. Wenn man nun den öffentlichen Schlüssel im XML Format übertragen möchte, enthält dieser u.a. Symbole wie <, >. Diese werden aber vom Server als Steuerzeichen betrachtet, deswegen müssen wir diese vorher escapen, was wir mittels Uri.EscapeDataString() tun. Beim Verschlüsseln eines Strings mit RSA ist die Ausgabe sehr kryptisch, es entstehen viele Sonderzeichen. Diese können nicht alle mit UTF8 dargestellt werden, deshalb konvertieren wir die Ausgabe mittels Convert.ToBase64String() in einen Base64 String. Der Server muss diesen dann zurückkonvertieren. Auch diesen String escapen wir, um eventuelle Steuerzeichen korrekt zu übertragen.

Nun zum Code, ich möchte ein kleines Beispielprojekt vorstellen. Das C# Programm und das PHP Skript erstellen beide ein Public / Private Schlüssel Paar und teilen dem anderen ihren öffentlichen Schlüssel mit. Dann verschlüsseln sie jeweils eine Nachricht mit diesem, schicken diese und der Empfänger entschlüsselt sie mit seinem privaten Schlüssel.
Das PHP Skript sieht so aus (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"]));
}
?>

Wir arbeiten mit Sessions (beachtet hierzu den Post über Sessions mit C#). Das Skript wird vom C# Programm 2mal aufgerufen. Ist noch keine Session angelegt, erzeugt der Server ein Schlüsselpaar und speichert seinen privaten Schlüssel. Ebenfalls speichert er den gesendeten öffentlichen Schlüssel des Clients und gibt seinen öffentlichen mittels getPublicKey() aus, wobei das XML Format als Typ übergeben wird. Der vorige Aufruf von setPublicKey() ist wichtig, da sonst nichts ausgegeben wird.
Beim nächsten Aufruf dann wird mittels loadKey() der gespeicherte öffentliche Schlüssel des Clients im XML Format eingelesen und der Text "Hello client" verschlüsselt. Dieser wird zwecks Datenübertragung in einen Base64 String konvertiert und ausgegeben. Außerdem lädt der Server seinen privaten Schlüssel und entschlüsselt damit die Nachricht vom Client, welche ebenfalls als Base64 String übergeben wurde.

Der C# Code sieht wie folgt aus:

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 den Funktion RSADecrypt() und RSAEncrypt() ist die Übergabe von true als 2. Parameter wichtig, welcher die Benutzung von OAEP Padding einschaltet. (Dies ist die Standardeinstellung von phpseclib, könnte aber auch dort geändert werden).
Ansonsten habe ich die Funktionen HTTPPost()RSADecrypt() und RSAEncrypt() in vorigen Posts erläutert, weswegen ich hier nicht mehr darauf eingehen werde.
In der 3. Zeile der Form_Load() escapen wir den erzeugten Public Key und senden diesen an den Server. Dabei lesen wir dessen Antwort, den Public Key des Servers im XML Format, aus. Mit diesem verschlüsseln wir die Nachricht "Hello server" und schicken diese als Base64 String und escaped an den Server. Schließlich entschlüsseln wir die Nachricht vom Server.

Keine Kommentare:

Kommentar veröffentlichen