.NET Compact Framework Encryption And File Transmittal With Mobile Devices In C#

By Robbe D. Morris

Printer Friendly Version

Robbe Morris
Robbe & Melisa Morris
  Download C# Source Code
The topic of encrypting data can be a bit intimidating for new .NET developers.  Then throw in the need to develop applications for mobile devices, such as the iPAQ, using the Compact Framework, and the project might seem to be out of your skill level comfort zone.  Unfortunately, the decision to leave the .NET cryptography classes out of the Compact Framework only compounds the problem.  The good news is that the co-founder of NullSkull.com, Peter Bromberg, has written a nice companion article on this subject that contains some great information and useful links: Encrypted WebService Communications with CryptoAPI .  It demonstrates the basics of encrypting messages in web services.


I've opted to go at this from a more simplistic point of view to help beginners get off to the right start.  I'll stick to the bare basics of encrypting/decrypting strings and files.  The following set of code samples also references the same encryption API as the other article and the class used is from the Microsoft Signature Sample that shows you how to capture handwritten signatures and transmit them to another application over tcp/ip.  Rather than get into the details of the encryption itself, I thought I'd share a few basic methods for encrypting/decrypting data and transmitting it over http without the use of a web service.
The sample code below is taken from a Smart Device Application and tested on an iPAQ h1910 (source code is available for download via the link above).  While stepping through the code, you may want to use your actual mobile device instead of the emulator because it is working with files on disk.  The methods include: Encrypting a string to a file, decrypting a file to a string, encrypting a file to an encrypted file, decrypting a file to a normal file, and a special method designed to read a file and transmit it to an .ashx page on a web server with the WebRequest class.  Prior to testing the last method, you'll want to move fileupload.aspx under the root folder of your Web site.  You may also have to alter its LocalPath variable for where the upload files are written to along with granting write permission to the folder.
All of the code shown below works on both the mobile device as well as a PC or server.  There are certain method calls that don't work properly on mobile devices if called in the standard manner you are used to on the PC.  So, if there seems like a few extra steps, that is why.  That said, I'll let you review the code sample below and take what you need from it.
 
Form1.cs
 
using System;
using System.Drawing;
using System.Collections;
using System.Windows.Forms;
using System.Data;
using System.IO;

private string EncryptionKey = "eggheadcafekey";
private string LocalPath = @"\My Documents\";
   
#region Encrypt String To File
private void button1_Click(object sender, System.EventArgs e)
{
  string MyStringToEncrypt = "this is a normal string for encryption";
  string MyFile = this.LocalPath + "encryptstringtofile.dat";

  try
  {

    NullSkull.DataTransfer oTransfer = new NullSkull.DataTransfer();

    if (oTransfer.EncryptStringToFile(MyFile,MyStringToEncrypt,EncryptionKey) == true)
    {
      MessageBox.Show("String encrypted to: " + MyFile);
    }
    else
    {
      MessageBox.Show("Failed to encrypt string to: " + MyFile);
    }

   }
   catch (Exception err) { MessageBox.Show(err.Message); }

}
#endregion

#region Decrypt A File To String
private void button2_Click(object sender, System.EventArgs e)
{
  string MyString = "";
  string MyFile = this.LocalPath + "encryptstringtofile.dat";

  try
  {

    NullSkull.DataTransfer oTransfer = new NullSkull.DataTransfer();

    MyString = oTransfer.DecryptFileReturnString(MyFile,EncryptionKey); 
              
    MessageBox.Show(MyString);
             
  }
  catch (Exception err) { MessageBox.Show(err.Message); }
}
#endregion

#region Encrypt Normal File to Encrypted File
private void button3_Click(object sender, System.EventArgs e)
{
  string MyNormalFile = this.LocalPath + "normalfile.txt";
  string MyFile = this.LocalPath + "encrypted.dat";

  try
  {

    StreamWriter oLocalFile = new StreamWriter(MyNormalFile);
      
    oLocalFile.Write("this is a test string for the file");

    oLocalFile.Close();

    NullSkull.DataTransfer oTransfer = new NullSkull.DataTransfer();

    if (oTransfer.EncryptFile(MyNormalFile,MyFile,EncryptionKey) == true) 
    {
       // oTransfer.RemoveFile(MyNormalFile); 
       MessageBox.Show("Encrypted " + MyNormalFile + " to " + MyFile);
    }
    else
    {
       MessageBox.Show("Failed to encrypt " + MyNormalFile);
    }

  }
  catch (Exception err) { MessageBox.Show(err.Message); }
  }
#endregion

#region Decrypt File
private void button4_Click(object sender, System.EventArgs e)
{
  string MyNormalFile = this.LocalPath + "normalfile.txt";
  string MyFile = this.LocalPath + "encrypted.dat";

  try
  {

    NullSkull.DataTransfer oTransfer = new NullSkull.DataTransfer();

    if (oTransfer.DecryptFile(MyFile,MyNormalFile,EncryptionKey) == true) 
    {
      // oTransfer.RemoveFile(MyFile); 
      MessageBox.Show("Decrypted " + MyFile + " to " + MyNormalFile);
    }
    else
    {
      MessageBox.Show("Failed to decrypt " + MyFile);
    }

  }
  catch (Exception err) { MessageBox.Show(err.Message); }
}
#endregion

#region Upload File
private void button5_Click(object sender, System.EventArgs e)
{
  NullSkull.DataTransfer oTransfer = new NullSkull.DataTransfer();

  try
  {

    // Remember, the mobile device doesn't know what localhost is
    // in relationship to your PC
                
    string Url="http://192.168.0.100/fileupload.ashx";  
    string FileName="";
    int FilesSent=0;
 
    string[] oFiles = Directory.GetFiles(this.LocalPath,"*.dat");

    if (oFiles.Length == 0)
    {
      MessageBox.Show("There are no encrypted files to transmit.");
      return;
    }

    oTransfer.Hourglass(true);
 
    for(int i=0;i<oFiles.Length;i++)
    {
                    
      FileName = oTransfer.StripFileNameFromFolder(oFiles[i].ToString());

      if (oTransfer.Upload(this.LocalPath,FileName,Url) == true)
      {
        FilesSent++;
      }

    }

    MessageBox.Show(FilesSent.ToString() + " encrypted files transmitted successfully.");
  }
  catch (Exception err) 
  {
   if (err.Message == "WebException")
    {
      MessageBox.Show("Please connect this device to the network.");
    }
    else
    {
      MessageBox.Show("Transmit: " + err.Message); 
    }
  }
  finally {  oTransfer.Hourglass(false);  }
}
#endregion
   
 
DataTransfer.cs
 
 using System;
using System.Net;
using System.IO;
using System.Text; 
using System.Diagnostics; 
 

namespace NullSkull
{
 
  public class DataTransfer
  {
        
    public DataTransfer()
    {
  
    }

    #region Encrypt File
    public bool EncryptFile(string Filename1,string Filename2,string EncryptionKey)
    {
      bool Ret = false;
      string Contents="";
      ASCIIEncoding encoding = new ASCIIEncoding(); 
      try
      {
              if (File.Exists(Filename1) == false) { return false; }  
              StreamReader oLocalFile = new StreamReader(Filename1);
              Contents = oLocalFile.ReadToEnd();
              oLocalFile.Close(); 
              byte[] encryptData = Common.Crypto.Encrypt(EncryptionKey,encoding.GetBytes(Contents));
              FileStream fs = new FileStream(Filename2, FileMode.Create);
              BinaryWriter w = new BinaryWriter(fs);
              w.Write(encryptData); 
              w.Close();
              fs.Close();
              Ret = true;
   }
   catch (Exception) { throw; }
   return Ret;
  }
  #endregion

        #region Encrypt String To File
        public bool EncryptStringToFile(string Filename,string FileContents,string EncryptionKey)
        {
            bool Ret = false;
   
            ASCIIEncoding encoding = new ASCIIEncoding(); 
            try
            {
                if (File.Exists(Filename) == true) { File.Delete(Filename);  }  

                byte[] encryptData = Common.Crypto.Encrypt(EncryptionKey,encoding.GetBytes(FileContents));
                FileStream fs = new FileStream(Filename, FileMode.Create);
                BinaryWriter w = new BinaryWriter(fs);
                w.Write(encryptData); 
                w.Close();
                fs.Close();
                Ret = true;
            }
            catch (Exception) { throw; }
            return Ret;
        }
        #endregion

  #region Decrypt File
  public bool DecryptFile(string Filename1,string Filename2,string EncryptionKey)
  {
   bool Ret = false;
   ASCIIEncoding encoding = new ASCIIEncoding(); 
   try
   {
         if (File.Exists(Filename1) == false) { return false; }  
         FileStream fr = new FileStream(Filename1, FileMode.Open);
         BinaryReader r = new BinaryReader(fr);
         byte[] decryptedData = Common.Crypto.Decrypt(EncryptionKey,r.ReadBytes((int)fr.Length));   
         r.Close();
         fr.Close();
         StreamWriter oLocalFile = new StreamWriter(Filename2);
         oLocalFile.Write(encoding.GetString(decryptedData,0,decryptedData.Length));
         oLocalFile.Close();
          Ret = true;
   }
   catch (Exception) { throw; }
   return Ret;
  }
  #endregion

  #region Decrypt File Return String
  public string DecryptFileReturnString(string Filename1,string EncryptionKey)
  {
   string Ret = "";
   ASCIIEncoding encoding = new ASCIIEncoding(); 
   try
   {
    if (File.Exists(Filename1) == false) { return Ret; } 
    FileStream fr = new FileStream(Filename1, FileMode.Open);
    BinaryReader r = new BinaryReader(fr);
    byte[] decryptedData = Common.Crypto.Decrypt(EncryptionKey,r.ReadBytes((int)fr.Length));   
    r.Close();
    fr.Close();
    Ret = encoding.GetString(decryptedData,0,decryptedData.Length);
   }
   catch (Exception) { throw; }
   return Ret;
  }
  #endregion

  #region Upload
  public bool Upload(string FilePath,string FileName,string Url)
  {
   string BytesConfirmedReceived="";
   int BytesSent=0;
   bool Ret=false;
   ASCIIEncoding encoding = new ASCIIEncoding(); 

   try
   {

     if (File.Exists(FilePath + FileName) == false) { return true; } 
     
     FileInfo oInfo = new FileInfo(FilePath + FileName);

     BytesSent = Convert.ToInt32(oInfo.Length.ToString());

     Url += "?myfile=" + FileName.Trim();

     FileStream fr = new FileStream(FilePath + FileName, FileMode.Open);

     BinaryReader r = new BinaryReader(fr);
     
     byte[] FileContents = r.ReadBytes((int)fr.Length);

     r.Close();

     fr.Close();

     WebRequest oRequest = WebRequest.Create(Url);
              
     oRequest.Method = "POST";
 
     oRequest.Timeout=15000;

     oRequest.ContentLength = FileContents.Length;
     
     Stream oStream = oRequest.GetRequestStream();

     BinaryWriter oWriter = new BinaryWriter(oStream);
     
     oWriter.Write(FileContents);
     
     oWriter.Close();

     oStream.Close();

     WebResponse oResponse = oRequest.GetResponse();

     BytesConfirmedReceived = new StreamReader(oResponse.GetResponseStream(),
	                                            Encoding.Default).ReadToEnd();
           
     oResponse.Close();
     
     if (BytesSent.ToString() == BytesConfirmedReceived.Trim())
     {
                Ret = true;
     }

      }
      catch (Exception)
   {
    throw;
   }
            return Ret;
 }
  #endregion

        #region Remove File
        public bool RemoveFile(string FileName)
        {
            bool Ret=false;
            try
            {
                if (File.Exists(FileName) == false) { return true; }
                File.Delete(FileName);
                Ret=true;
            }
            catch (Exception) { throw; }
            return Ret;
        }
        #endregion

        #region Strip File Name From Folder
        public string StripFileNameFromFolder(string FullFileName)
        {

            int nPos=0;
            string sNewVal="";
   
            try
            {

                FullFileName = FullFileName.Trim();
    
                if (FullFileName.Length <1) { return FullFileName; }

                nPos = FullFileName.LastIndexOf(@"\") + 1;
    
                if (nPos<1) { return FullFileName; }

                sNewVal = FullFileName.Substring(nPos,FullFileName.Length - nPos);
                     
            }
            catch (Exception e) { sNewVal = e.Message; }
   
            return sNewVal;
        }
        #endregion

        #region Hourglass
        public void Hourglass(bool Show)
        {
            if (Show == true)
            {
                System.Windows.Forms.Cursor.Current = System.Windows.Forms.Cursors.WaitCursor;
            }
            else
            {
                System.Windows.Forms.Cursor.Current = System.Windows.Forms.Cursors.Default; 
            }
            return;
        }
        #endregion

 }
}
 
Crypto.cs For Compact Framework
 
 /*
  Encryption Class From Microsoft Signature Sample
 --
 Uses crypto API functions to encrypt and decrypt data. A passphrase 
 string is used to create a 128-bit hash that is used to create a 
 40-bit crypto key. The same key is required to encrypt and decrypt 
 the data.
*/

using System;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Text;

namespace Common
{
 /// <summary>
 /// Encrypts and decrypts data using the crypto APIs.
 /// </summary>
 public class Crypto
 {
  // API functions
  private class WinApi
  {
   #region Crypto API imports
  
   private const uint ALG_CLASS_HASH = (4 << 13);
   private const uint ALG_TYPE_ANY = (0);
   private const uint ALG_CLASS_DATA_ENCRYPT = (3 << 13);
   private const uint ALG_TYPE_STREAM = (4 << 9);
   private const uint ALG_TYPE_BLOCK = (3 << 9);

   private const uint ALG_SID_DES = 1;
   private const uint ALG_SID_RC4 = 1;
   private const uint ALG_SID_RC2 = 2;
   private const uint ALG_SID_MD5 = 3;

   public const string MS_DEF_PROV = "Microsoft Base Cryptographic Provider v1.0";
   
   public const uint PROV_RSA_FULL = 1;
   public const uint CRYPT_VERIFYCONTEXT = 0xf0000000;
   public const uint CRYPT_EXPORTABLE = 0x00000001;

   public static readonly uint CALG_MD5 = (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_MD5);
   public static readonly uint CALG_DES = (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK
                                          | ALG_SID_DES);
   public static readonly uint CALG_RC2 = (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK
                                         | ALG_SID_RC2);
   public static readonly uint CALG_RC4 = (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_STREAM |
                                           ALG_SID_RC4);

  // Use these dlls for mobile devices only

    const string CryptDll = "coredll.dll";
    const string KernelDll = "coredll.dll";

 // Use these dlls for all other pcs and servers

  //  const string CryptDll = "advapi32.dll";
  // const string KernelDll = "kernel32.dll";
   
   [DllImport(CryptDll)] 
   public static extern bool CryptAcquireContext(
    ref IntPtr phProv, string pszContainer, string pszProvider,
    uint dwProvType, uint dwFlags);

   [DllImport(CryptDll)] 
   public static extern bool CryptReleaseContext( 
    IntPtr hProv, uint dwFlags);

   [DllImport(CryptDll)] 
   public static extern bool CryptDeriveKey(
    IntPtr hProv, uint Algid, IntPtr hBaseData, 
    uint dwFlags, ref IntPtr phKey);
    
   [DllImport(CryptDll)] 
   public static extern bool CryptCreateHash(
    IntPtr hProv, uint Algid, IntPtr hKey, 
    uint dwFlags, ref IntPtr phHash);

   [DllImport(CryptDll)] 
   public static extern bool CryptHashData(
    IntPtr hHash, byte[] pbData, 
    uint dwDataLen, uint dwFlags);
    
   [DllImport(CryptDll)] 
   public static extern bool CryptEncrypt(
    IntPtr hKey, IntPtr hHash, bool Final, uint dwFlags, 
    byte[] pbData, ref uint pdwDataLen, uint dwBufLen);

   [DllImport(CryptDll)] 
   public static extern bool CryptDecrypt(
    IntPtr hKey, IntPtr hHash, bool Final, uint dwFlags, 
    byte[] pbData, ref uint pdwDataLen);

   [DllImport(CryptDll)] 
   public static extern bool CryptDestroyHash(IntPtr hHash);

   [DllImport(CryptDll)] 
   public static extern bool CryptDestroyKey(IntPtr hKey);

   #endregion

   #region Error reporting imports
 
   public const uint FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;

   [DllImport(KernelDll)]
   public static extern uint GetLastError();

   [DllImport(KernelDll)]
   public static extern uint FormatMessage(
    uint dwFlags, string lpSource, uint dwMessageId,
    uint dwLanguageId, StringBuilder lpBuffer, uint nSize,
    string [] Arguments);

   #endregion    
  }
 
  // all static methods
  private Crypto()
  {
  }
  
  /// <summary>
  /// Encrypt data. Use passphrase to generate the encryption key. 
  /// Returns a byte array that contains the encrypted data.
  /// </summary>
  static public byte[] Encrypt(string passphrase, byte[] data)
  {
   // holds encrypted data
   byte[] buffer = null;  

   // crypto handles
   IntPtr hProv = IntPtr.Zero;
   IntPtr hKey = IntPtr.Zero;

   try
   {
    // get crypto provider, specify the provider (3rd argument)
    // instead of using default to ensure the same provider is 
    // used on client and server
    if (!WinApi.CryptAcquireContext(ref hProv, null, WinApi.MS_DEF_PROV, 
     WinApi.PROV_RSA_FULL, WinApi.CRYPT_VERIFYCONTEXT))
     Failed("CryptAcquireContext");
    
    // generate encryption key from passphrase
    hKey = GetCryptoKey(hProv, passphrase);

    // determine how large of a buffer is required
    // to hold the encrypted data
    uint dataLength = (uint)data.Length;
    uint bufLength = (uint)data.Length;
    if (!WinApi.CryptEncrypt(hKey, IntPtr.Zero, true, 
     0, null, ref dataLength, bufLength))
     Failed("CryptEncrypt");
    
    // allocate and fill buffer with encrypted data
    buffer = new byte[dataLength];
    Buffer.BlockCopy(data, 0, buffer, 0, data.Length);
    
    dataLength = (uint)data.Length;
    bufLength = (uint)buffer.Length;
    if (!WinApi.CryptEncrypt(hKey, IntPtr.Zero, true, 
     0, buffer, ref dataLength, bufLength))
     Failed("CryptEncrypt");
   }
   
   finally
   {
    // release crypto handles
    if (hKey != IntPtr.Zero)
     WinApi.CryptDestroyKey(hKey);

    if (hProv != IntPtr.Zero)
     WinApi.CryptReleaseContext(hProv, 0);
   }
   
   return buffer;
  }


  /// <summary>
  /// Decrypt data. Use passphrase to generate the encryption key. 
  /// Returns a byte array that contains the decrypted data.
  /// </summary>
  static public byte[] Decrypt(string passphrase, byte[] data)
  {
   // make a copy of the encrypted data
   byte[] dataCopy = data.Clone() as byte[];
   
   // holds the decrypted data
   byte[] buffer = null;
   
   // crypto handles
   IntPtr hProv = IntPtr.Zero;
   IntPtr hKey = IntPtr.Zero;
   
   try
   {
    // get crypto provider, specify the provider (3rd argument)
    // instead of using default to ensure the same provider is 
    // used on client and server
    if (!WinApi.CryptAcquireContext(ref hProv, null, WinApi.MS_DEF_PROV, 
     WinApi.PROV_RSA_FULL, WinApi.CRYPT_VERIFYCONTEXT))
     Failed("CryptAcquireContext");
   
    // generate encryption key from the passphrase
    hKey = GetCryptoKey(hProv, passphrase);

    // decrypt the data
    uint dataLength = (uint)dataCopy.Length;
    if (!WinApi.CryptDecrypt(hKey, IntPtr.Zero, true, 
     0, dataCopy, ref dataLength))
     Failed("CryptDecrypt");
    
    // copy to a buffer that is returned to the caller
    // the decrypted data size might be less then
    // the encrypted size
    buffer = new byte[dataLength];
    Buffer.BlockCopy(dataCopy, 0, buffer, 0, (int)dataLength);
   }
   
   finally
   {
    // release crypto handles
    if (hKey != IntPtr.Zero)
     WinApi.CryptDestroyKey(hKey);

    if (hProv != IntPtr.Zero)
     WinApi.CryptReleaseContext(hProv, 0);
   }
   
   return buffer;
  }
  

  /// <summary>
  /// Create a crypto key form a passphrase. This key is 
  /// used to encrypt and decrypt data.
  /// </summary>
  static private IntPtr GetCryptoKey(IntPtr hProv, string passphrase)
  {
   // crypto handles
   IntPtr hHash = IntPtr.Zero;
   IntPtr hKey = IntPtr.Zero;
   
   try
   {
    // create 128 bit hash object
    if (!WinApi.CryptCreateHash(hProv, 
     WinApi.CALG_MD5, IntPtr.Zero, 0, ref hHash))
     Failed("CryptCreateHash");
    
    // add passphrase to hash
    byte[] keyData = ASCIIEncoding.ASCII.GetBytes(passphrase);
    if (!WinApi.CryptHashData(hHash, keyData, (uint)keyData.Length, 0))
     Failed("CryptHashData");
     
    // create 40 bit crypto key from passphrase hash
    if (!WinApi.CryptDeriveKey(hProv, WinApi.CALG_RC2, 
     hHash, WinApi.CRYPT_EXPORTABLE, ref hKey))
     Failed("CryptDeriveKey");
   }
   
   finally
   {
    // release hash object
    if (hHash != IntPtr.Zero)
     WinApi.CryptDestroyHash(hHash);
   }
   
   return hKey;
  }


  /// <summary>
  /// Throws SystemException with GetLastError information.
  /// </summary>
  static private void Failed(string command)
  {
   uint lastError = WinApi.GetLastError();
   StringBuilder sb = new StringBuilder(500);

   try
   {
    // get message for last error
    WinApi.FormatMessage(WinApi.FORMAT_MESSAGE_FROM_SYSTEM, 
     null, lastError, 0, sb, 500, null);
   }
   catch
   {
    // error calling FormatMessage
    sb.Append("N/A.");
   }
     
   throw new SystemException(
    string.Format("{0} failed.\r\nLast error - 0x{1:x}.\r\nError message - {2}",
    command, lastError, sb.ToString()));
  }
 }
}

    
 
FileUpload.ashx
<%@ WebHandler Language="C#" Class="CustomHandler.FileUpload" %>
using System;
using System.Xml;
using System.Data;
using System.Web;
using System.IO;
using System.Text;
using System.Runtime.InteropServices;
using System.Drawing;
 


namespace CustomHandler
{

   public class FileUpload : IHttpHandler
   { 
      public void ProcessRequest(HttpContext oContext)
      {

           int BytesSent = 0;
           int MaxBytesAccepted=10000;
           string sXml="";
           string LocalPath = @"C:\Inetpub\wwwroot\";
           string MyFile="";
           string Key="eggheadcafekey";
           

           try 
          {
   
               MyFile = LocalPath + oContext.Request["myfile"].ToString().Trim();

               ASCIIEncoding encoding = new ASCIIEncoding(); 

               BytesSent = oContext.Request.TotalBytes;

               // Block out file uploads that are too big

               if (BytesSent > MaxBytesAccepted) {  oContext.Response.Write("0"); return; }

               byte[] InComingBinaryArray = oContext.Request.BinaryRead(oContext.Request.TotalBytes);

               byte[] decryptedData = Common.Crypto.Decrypt(Key,InComingBinaryArray);   

             //  Validate the data inside.  For instance, if you know the data is Xml, you
             //  attempt to load it.  If you know some of its contents, you could check
             //  for that as well.

             /*
               sXml = encoding.GetString(decryptedData);
               XmlDocument oXml = new XmlDocument();
               oXml.LoadXml(sXml);   
               if (oXml.ChildNodes.Count == 0)  {  oContext.Response.Write("0"); return; }
             */

               if (File.Exists(MyFile) == true) 
               {
                    File.Delete(MyFile); 
                }

                FileStream fs = new FileStream(MyFile, FileMode.CreateNew);
                BinaryWriter w = new BinaryWriter(fs);
                w.Write(InComingBinaryArray); 
                w.Close();
                fs.Close();

                FileInfo oInfo = new FileInfo(MyFile);

                oContext.Response.Write(oInfo.Length.ToString());

           }
          catch (Exception err)  {  oContext.Response.Write(err.Message); }
 
      }

      public bool IsReusable { get { return true; } }

 }
}




/*
 Signature Sample Cryptography class for Microsoft Sample
 --
 Uses crypto API functions to encrypt and decrypt data. A passphrase 
 string is used to create a 128-bit hash that is used to create a 
 40-bit crypto key. The same key is required to encrypt and decrypt 
 the data.
*/



namespace Common
{
    /// <summary>
    /// Encrypts and decrypts data using the crypto APIs.
    /// </summary>
    public class Crypto
    {
        // API functions
        private class WinApi
        {
            #region Crypto API imports
  
            private const uint ALG_CLASS_HASH = (4 << 13);
            private const uint ALG_TYPE_ANY = (0);
            private const uint ALG_CLASS_DATA_ENCRYPT = (3 << 13);
            private const uint ALG_TYPE_STREAM = (4 << 9);
            private const uint ALG_TYPE_BLOCK = (3 << 9);

            private const uint ALG_SID_DES = 1;
            private const uint ALG_SID_RC4 = 1;
            private const uint ALG_SID_RC2 = 2;
            private const uint ALG_SID_MD5 = 3;

            public const string MS_DEF_PROV = "Microsoft Base Cryptographic Provider v1.0";
   
            public const uint PROV_RSA_FULL = 1;
            public const uint CRYPT_VERIFYCONTEXT = 0xf0000000;
            public const uint CRYPT_EXPORTABLE = 0x00000001;

            public static readonly uint CALG_MD5 = (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_MD5);
            public static readonly uint CALG_DES = (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_DES);
            public static readonly uint CALG_RC2 = (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_RC2);
            public static readonly uint CALG_RC4 = (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_STREAM | ALG_SID_RC4);

            const string CryptDll = "advapi32.dll";
            const string KernelDll = "kernel32.dll";
    
            [DllImport(CryptDll)] 
            public static extern bool CryptAcquireContext(
                ref IntPtr phProv, string pszContainer, string pszProvider,
                uint dwProvType, uint dwFlags);

            [DllImport(CryptDll)] 
            public static extern bool CryptReleaseContext( 
                IntPtr hProv, uint dwFlags);

            [DllImport(CryptDll)] 
            public static extern bool CryptDeriveKey(
                IntPtr hProv, uint Algid, IntPtr hBaseData, 
                uint dwFlags, ref IntPtr phKey);
    
            [DllImport(CryptDll)] 
            public static extern bool CryptCreateHash(
                IntPtr hProv, uint Algid, IntPtr hKey, 
                uint dwFlags, ref IntPtr phHash);

            [DllImport(CryptDll)] 
            public static extern bool CryptHashData(
                IntPtr hHash, byte[] pbData, 
                uint dwDataLen, uint dwFlags);
    
            [DllImport(CryptDll)] 
            public static extern bool CryptEncrypt(
                IntPtr hKey, IntPtr hHash, bool Final, uint dwFlags, 
                byte[] pbData, ref uint pdwDataLen, uint dwBufLen);

            [DllImport(CryptDll)] 
            public static extern bool CryptDecrypt(
                IntPtr hKey, IntPtr hHash, bool Final, uint dwFlags, 
                byte[] pbData, ref uint pdwDataLen);

            [DllImport(CryptDll)] 
            public static extern bool CryptDestroyHash(IntPtr hHash);

            [DllImport(CryptDll)] 
            public static extern bool CryptDestroyKey(IntPtr hKey);

            #endregion

            #region Error reporting imports
 
            public const uint FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;

            [DllImport(KernelDll)]
            public static extern uint GetLastError();

            [DllImport(KernelDll)]
            public static extern uint FormatMessage(
                uint dwFlags, string lpSource, uint dwMessageId,
                uint dwLanguageId, StringBuilder lpBuffer, uint nSize,
                string [] Arguments);

            #endregion    
        }
 
        // all static methods
        private Crypto()
        {
        }
  
        /// <summary>
        /// Encrypt data. Use passphrase to generate the encryption key. 
        /// Returns a byte array that contains the encrypted data.
        /// </summary>
        static public byte[] Encrypt(string passphrase, byte[] data)
        {
            // holds encrypted data
            byte[] buffer = null;  

            // crypto handles
            IntPtr hProv = IntPtr.Zero;
            IntPtr hKey = IntPtr.Zero;

            try
            {
                // get crypto provider, specify the provider (3rd argument)
                // instead of using default to ensure the same provider is 
                // used on client and server
                if (!WinApi.CryptAcquireContext(ref hProv, null, WinApi.MS_DEF_PROV, 
                    WinApi.PROV_RSA_FULL, WinApi.CRYPT_VERIFYCONTEXT))
                    Failed("CryptAcquireContext");
    
                // generate encryption key from passphrase
                hKey = GetCryptoKey(hProv, passphrase);

                // determine how large of a buffer is required
                // to hold the encrypted data
                uint dataLength = (uint)data.Length;
                uint bufLength = (uint)data.Length;
                if (!WinApi.CryptEncrypt(hKey, IntPtr.Zero, true, 
                    0, null, ref dataLength, bufLength))
                    Failed("CryptEncrypt");
    
                // allocate and fill buffer with encrypted data
                buffer = new byte[dataLength];
                Buffer.BlockCopy(data, 0, buffer, 0, data.Length);
    
                dataLength = (uint)data.Length;
                bufLength = (uint)buffer.Length;
                if (!WinApi.CryptEncrypt(hKey, IntPtr.Zero, true, 
                    0, buffer, ref dataLength, bufLength))
                    Failed("CryptEncrypt");
            }
   
            finally
            {
                // release crypto handles
                if (hKey != IntPtr.Zero)
                    WinApi.CryptDestroyKey(hKey);

                if (hProv != IntPtr.Zero)
                    WinApi.CryptReleaseContext(hProv, 0);
            }
   
            return buffer;
        }


        /// <summary>
        /// Decrypt data. Use passphrase to generate the encryption key. 
        /// Returns a byte array that contains the decrypted data.
        /// </summary>
        static public byte[] Decrypt(string passphrase, byte[] data)
        {
            // make a copy of the encrypted data
            byte[] dataCopy = data.Clone() as byte[];
   
            // holds the decrypted data
            byte[] buffer = null;
   
            // crypto handles
            IntPtr hProv = IntPtr.Zero;
            IntPtr hKey = IntPtr.Zero;
   
            try
            {
                // get crypto provider, specify the provider (3rd argument)
                // instead of using default to ensure the same provider is 
                // used on client and server
                if (!WinApi.CryptAcquireContext(ref hProv, null, WinApi.MS_DEF_PROV, 
                    WinApi.PROV_RSA_FULL, WinApi.CRYPT_VERIFYCONTEXT))
                    Failed("CryptAcquireContext");
   
                // generate encryption key from the passphrase
                hKey = GetCryptoKey(hProv, passphrase);

                // decrypt the data
                uint dataLength = (uint)dataCopy.Length;
                if (!WinApi.CryptDecrypt(hKey, IntPtr.Zero, true, 
                    0, dataCopy, ref dataLength))
                    Failed("CryptDecrypt");
    
                // copy to a buffer that is returned to the caller
                // the decrypted data size might be less then
                // the encrypted size
                buffer = new byte[dataLength];
                Buffer.BlockCopy(dataCopy, 0, buffer, 0, (int)dataLength);
            }
   
            finally
            {
                // release crypto handles
                if (hKey != IntPtr.Zero)
                    WinApi.CryptDestroyKey(hKey);

                if (hProv != IntPtr.Zero)
                    WinApi.CryptReleaseContext(hProv, 0);
            }
   
            return buffer;
        }
  

        /// <summary>
        /// Create a crypto key form a passphrase. This key is 
        /// used to encrypt and decrypt data.
        /// </summary>
        static private IntPtr GetCryptoKey(IntPtr hProv, string passphrase)
        {
            // crypto handles
            IntPtr hHash = IntPtr.Zero;
            IntPtr hKey = IntPtr.Zero;
   
            try
            {
                // create 128 bit hash object
                if (!WinApi.CryptCreateHash(hProv, 
                    WinApi.CALG_MD5, IntPtr.Zero, 0, ref hHash))
                    Failed("CryptCreateHash");
    
                // add passphrase to hash
                byte[] keyData = ASCIIEncoding.ASCII.GetBytes(passphrase);
                if (!WinApi.CryptHashData(hHash, keyData, (uint)keyData.Length, 0))
                    Failed("CryptHashData");
     
                // create 40 bit crypto key from passphrase hash
                if (!WinApi.CryptDeriveKey(hProv, WinApi.CALG_RC2, 
                    hHash, WinApi.CRYPT_EXPORTABLE, ref hKey))
                    Failed("CryptDeriveKey");
            }
   
            finally
            {
                // release hash object
                if (hHash != IntPtr.Zero)
                    WinApi.CryptDestroyHash(hHash);
            }
   
            return hKey;
        }


        /// <summary>
        /// Throws SystemException with GetLastError information.
        /// </summary>
        static private void Failed(string command)
        {
            uint lastError = WinApi.GetLastError();
            StringBuilder sb = new StringBuilder(500);

            try
            {
                // get message for last error
                WinApi.FormatMessage(WinApi.FORMAT_MESSAGE_FROM_SYSTEM, 
                    null, lastError, 0, sb, 500, null);
            }
            catch
            {
                // error calling FormatMessage
                sb.Append("N/A.");
            }
     
            throw new SystemException(
                string.Format("{0} failed.\r\nLast error - 0x{1:x}.\r\nError message - {2}",
                command, lastError, sb.ToString()));
        }
    }
}


Robbe has been a Microsoft MVP in C# since 2004.  He is also the co-founder of NullSkull.com which provides .NET articles, book reviews, software reviews, and software download and purchase advice.