lørdag den 11. december 2010

Asymmetric Encryption on Client and Server

In order to use SSL you need at dedicated server. This is not cost effective so the solution is to encrypt the stuff yourself.

This can be done but is not an easy job due to the fact that you need to find code that can encrypt and decrypt both on client and server.

You need this server class

 using System;  
 using System.Collections.Generic;  
 using System.Linq;  
 using System.Web;  
 using System.Security.Cryptography;  
 using System.IO;  
 public class Cryptography  
 {  
   public static RSACryptoServiceProvider rsa;  
   public static void AssignParameter()  
   {  
     const int PROVIDER_RSA_FULL = 1;  
     const string CONTAINER_NAME = "SpiderContainer";  
     CspParameters cspParams;  
     cspParams = new CspParameters(PROVIDER_RSA_FULL);  
     cspParams.KeyContainerName = CONTAINER_NAME;  
     cspParams.Flags = CspProviderFlags.UseMachineKeyStore;  
     cspParams.ProviderName = "Microsoft Strong Cryptographic Provider";  
     rsa = new RSACryptoServiceProvider(cspParams);  
   }  
   public static string EncryptData(string data2Encrypt)  
   {  
     AssignParameter();  
     StreamReader reader = new StreamReader(System.Web.HttpContext.Current.Server.MapPath(@"App_Data\publickey.xml"));  
     string publicOnlyKeyXML = reader.ReadToEnd();  
     rsa.FromXmlString(publicOnlyKeyXML);  
     reader.Close();  
     //read plaintext, encrypt it to ciphertext  
     byte[] plainbytes = System.Text.Encoding.UTF8.GetBytes(data2Encrypt);  
     byte[] cipherbytes = rsa.Encrypt(plainbytes, false);  
     return Convert.ToBase64String(cipherbytes);  
   }  
   public static void AssignNewKey()  
   {  
     AssignParameter();  
     //provide public and private RSA params  
     StreamWriter writer = new StreamWriter(System.Web.HttpContext.Current.Server.MapPath(@"App_Data\privatekey.xml"));  
     string publicPrivateKeyXML = rsa.ToXmlString(true);  
     writer.Write(publicPrivateKeyXML);  
     writer.Close();  
     //provide public only RSA params  
     writer = new StreamWriter(System.Web.HttpContext.Current.Server.MapPath(@"App_Data\publickey.xml"));  
     string publicOnlyKeyXML = rsa.ToXmlString(false);  
     writer.Write(publicOnlyKeyXML);  
     writer.Close();  
   }  
   public static string DecryptData(string data2Decrypt)  
   {  
     if (data2Decrypt == null)  
       return null;  
     AssignParameter();  
     byte[] getpassword = Convert.FromBase64String(data2Decrypt);  
     StreamReader reader = new StreamReader(System.Web.HttpContext.Current.Server.MapPath(@"App_Data\privatekey.xml"));  
     string publicPrivateKeyXML = reader.ReadToEnd();  
     rsa.FromXmlString(publicPrivateKeyXML);  
     reader.Close();  
     //read ciphertext, decrypt it to plaintext  
     byte[] plain = rsa.Decrypt(getpassword, false);  
     return System.Text.Encoding.UTF8.GetString(plain).Replace(UniqueGuidForEncryption.ToString(), "");  
   }  
 }  

And you need this set of javascript files and a reference to them

http://www.e-keys.dk/js/RSA/jsbn.js
http://www.e-keys.dk/js/RSA/prng4.js
http://www.e-keys.dk/js/RSA/rng.js
http://www.e-keys.dk/js/RSA/rsa.js
http://www.e-keys.dk/js/RSA/base64.js

Add this code to the OnLoad

 if( !System.IO.File.Exists(Server.MapPath("App_Data/publickey.xml")) || Request.QueryString["newkey"] != null )  
   Cryptography.AssignNewKey();  


This will create the public and the private key xml file that is used to encrypt/decrypt the data before it is sent in the postback.

Add this script to you page

 function do_encrypt(val) {  
   if (val.length == 0)  
     return val;  
   var rsa = new RSAKey();  
   rsa.setPublic('', '');  
   var res = rsa.encrypt(val);  
   return hex2b64(res);  
 }  
 function hashAndEncrypt(val) {  
   var rsa = new RSAKey();  
   rsa.setPublic('', '');  
   var res = rsa.encrypt(val);  
   return hex2b64(res);  
 }  

and add this two methods to your code behind

 protected String RSAModulus  
 {  
   get  
   {  
     XmlDocument doc = new XmlDocument();  
     doc.Load(Server.MapPath("App_Data/publickey.xml"));  
     return BitConverter.ToString(Convert.FromBase64String(doc.SelectSingleNode("RSAKeyValue/Modulus").InnerText)).Replace("-", "");  
   }  
 }  
 protected String RSAExponent  
 {  
   get  
   {  
     XmlDocument doc = new XmlDocument();  
     doc.Load(Server.MapPath("App_Data/publickey.xml"));  
   return BitConverter.ToString(Convert.FromBase64String(doc.SelectSingleNode("RSAKeyValue/Exponent").InnerText)).Replace("-", "");  
   }  
 }  

This is needed because the client side encryption needs hexa-decimal keys and the server side uses Base64Strings.

You need to call DataBind() as the last thing in OnLoad.

The last thing you need to do is encrypt the content of the textboxes. This is done in the click event of the button like this

 document.getElementById("textbox-id").value = do_encrypt(document.getElementById("textbox-id").value)  


You need to set the value for one and each of the textboxes that you need encrypted.

On the server you just call Cryptography.DecryptData(text) to decrypt the content of the textbox.

This should be it.

4 kommentarer:

  1. The UniqueGuidForEncryption was used to create a different output when incrypting the data but I found out that it was not neccesary because the encryption did this by iteself... you can remove the replace using this UniqueGuidForEncryption.
    The code should work.. I am using it on e-keys.dk when creating a user... try it out... you will see that text of text boxes are incrypted before sent to the server....

    SvarSlet
  2. How to get rsa.setPublic('', '');
    Error: Invalid RSA public key

    SvarSlet