Click here to Skip to main content
15,886,258 members
Articles / Programming Languages / C#
Article

Symmetric Cryptography and Hashing in C#

Rate me:
Please Sign up or sign in to vote.
3.63/5 (15 votes)
12 Aug 20043 min read 165.5K   4.8K   57   20
An article on symmetric cryptography and hashing.

Image 1

Introduction

Cryptography is a way of protecting data from being viewed or modified by unauthorized individuals. It is used to achieve Confidentiality, Data Integrity, and Authentication (CIA). There are two basic types of cryptography: asymmetric and symmetric cryptography. In symmetric cryptography, there are two different keys for encryption and decryption. However, in symmetric cryptography, we use the same keys for both encrypting and decrypting data. We shall look at symmetric cryptography, specifically a C# .NET implementation. The discussion of asymmetric cryptography is beyond the scope of this article.

The symmetric cryptography class can be used for two-way encryption of data. It is clearly written with three constructors, and overridable public methods to support polymorphism. It supports four algorithms: Rijndael, RC2, DES, and TripleDES. In addition, I have included a Hash class in the same namespace. The Hash class follows similar pattern and supports the following algorithms: MD5, SHA1, SHA256, SHA384, and SHA512.

Background

I have seen a number of codes and articles on symmetric cryptography, but they all seem to have the problem of generating illegal or invalid encryption key. To enable us do this right, we must remember that a legal key size must be:

  1. Greater than or equal to minimum size for a chosen cryptographic algorithm.
  2. Equal to a valid size for a chosen cryptographic algorithm.
  3. 8 bytes for DES; 16 or 24 bytes for TripleDES; 16, 24, or 32 for Rijndael.

Knowing and understanding these facts will help us to write a working generic code for all the algorithms.

Using the code

In this edition, I used the PasswordDeriveBytes method in the System.Security namespace for best security and also included a SALT property, in line with the best practices. Below is a console application showing how to use the Cryptography component or class:

C#
using System;

using CodeProject.Chidi.Cryptography;

namespace CodeProject.Chidi.TestConsole

{

 /// <summary>

 /// For testing components.

 /// </summary>

 class clsTest

 {

  /// <summary>

  /// The main entry point for the application.

  /// </summary>

  [STAThread]

  static void Main(string[] args)

  {

   SymCryptography cryptic = new SymCryptography();

   string plainText = "Chidi Ezeukwu";

   cryptic.Key = "wqdj~yriu!@*k0_^fa7431%p$#=@hd+&";

   Console.WriteLine("The Encryption Component Test\n");

   Console.WriteLine("=============================\n");

   Console.WriteLine("Plain text: " + plainText);

   Console.WriteLine("Key: " + cryptic.Key + "\n");


   Console.WriteLine("Using default encryption algorithm:");

   string TestEnc = cryptic.Encrypt(plainText);

   string TestDec = cryptic.Decrypt(TestEnc);

   Console.WriteLine("Encrypted text: " + TestEnc+ "\n");

   Console.WriteLine("Using RC2 algorithm:");

   cryptic = new SymCryptography("rc2");

   TestEnc = cryptic.Encrypt(plainText);

   Console.WriteLine("Encrypted text: " + TestEnc + "\n");

   Console.WriteLine("Using DES algorithm:");

   cryptic = new SymCryptography("DES");

   TestEnc = cryptic.Encrypt(plainText);

   Console.WriteLine("Encrypted text: " + TestEnc + "\n");


   Console.WriteLine("Using TripleDES algorithm:");

   cryptic = new SymCryptography("TripleDES");

   TestEnc = cryptic.Encrypt(plainText);

   Console.WriteLine("Encrypted text: " + TestEnc + "\n");


   Hash hash = new Hash("SHA1");

   string TestHash = hash.Encrypt(plainText);

   Console.WriteLine("Using SHA1 hashing algorithm:");

   Console.WriteLine("Hashed text: " + TestHash + "\n");

   Console.Read();

  }

 }

}

The source code:

C#
using System;
using System.IO;
using System.Text;
using System.Security.Cryptography;


namespace CodeProject.Chidi.Cryptography
{
 #region Symmetric cryptography class...

 /// <SUMMARY>Contains methods and properties for 
 ///  two-way encryption and decryption</SUMMARY>
 /// <AUTHOR>Chidi C. Ezeukwu</AUTHOR>
 /// <WRITTEN>May 24, 2004</WRITTEN>
 /// <UPDATED>Aug 07, 2004</UPDATED>
 public class SymCryptography
 {
  #region Private members...

  private string mKey = string.Empty;
  private string mSalt = string.Empty;
  private ServiceProviderEnum mAlgorithm;
  private SymmetricAlgorithm mCryptoService;

  private void SetLegalIV()
  {
   // Set symmetric algorithm
   switch(mAlgorithm)
   {
    case ServiceProviderEnum.Rijndael:
     mCryptoService.IV = new byte[] {0xf, 0x6f, 0x13, 0x2e, 0x35, 
 0xc2, 0xcd, 0xf9, 0x5, 0x46, 0x9c, 0xea, 0xa8, 0x4b, 0x73,0xcc};
     break;
    default:
     mCryptoService.IV = new byte[] {0xf, 0x6f, 0x13, 0x2e,
      0x35, 0xc2, 0xcd, 0xf9};
     break;
   }
  }

  #endregion

  #region Public interfaces...

  public enum ServiceProviderEnum: int
  {
   // Supported service providers
   Rijndael,
   RC2,
   DES,
   TripleDES
  }
    
  public SymCryptography()
  {
   // Default symmetric algorithm
   mCryptoService = new RijndaelManaged();
   mCryptoService.Mode = CipherMode.CBC;
   mAlgorithm = ServiceProviderEnum.Rijndael;
  }

  public SymCryptography(ServiceProviderEnum serviceProvider)
  { 
   // Select symmetric algorithm
   switch(serviceProvider)
   {
    case ServiceProviderEnum.Rijndael:
     mCryptoService = new RijndaelManaged();
     mAlgorithm = ServiceProviderEnum.Rijndael;
     break;
    case ServiceProviderEnum.RC2:
     mCryptoService = new RC2CryptoServiceProvider();
     mAlgorithm = ServiceProviderEnum.RC2;
     break;
    case ServiceProviderEnum.DES:
     mCryptoService = new DESCryptoServiceProvider();
     mAlgorithm = ServiceProviderEnum.DES;
     break;
    case ServiceProviderEnum.TripleDES:
     mCryptoService = new TripleDESCryptoServiceProvider();
     mAlgorithm = ServiceProviderEnum.TripleDES;
     break;
   }
   mCryptoService.Mode = CipherMode.CBC;
  }

  public SymCryptography(string serviceProviderName)
  {
   try
   {
    // Select symmetric algorithm
    switch(serviceProviderName.ToLower())
    {
     case "rijndael":
      serviceProviderName = "Rijndael"; 
      mAlgorithm = ServiceProviderEnum.Rijndael;
      break;
     case "rc2":
      serviceProviderName = "RC2";
      mAlgorithm = ServiceProviderEnum.RC2;
      break;
     case "des":
      serviceProviderName = "DES";
      mAlgorithm = ServiceProviderEnum.DES;
      break;
     case "tripledes":
      serviceProviderName = "TripleDES";
      mAlgorithm = ServiceProviderEnum.TripleDES;
      break;
    }

    // Set symmetric algorithm
    mCryptoService = (SymmetricAlgorithm)
      CryptoConfig.CreateFromName(serviceProviderName);
    mCryptoService.Mode = CipherMode.CBC;
   }
   catch
   {
    throw;
   }
  }

  public virtual byte[] GetLegalKey()
  {
   // Adjust key if necessary, and return a valid key
   if (mCryptoService.LegalKeySizes.Length > 0)
   {
    // Key sizes in bits
    int keySize = mKey.Length * 8;
    int minSize = mCryptoService.LegalKeySizes[0].MinSize;
    int maxSize = mCryptoService.LegalKeySizes[0].MaxSize;
    int skipSize = mCryptoService.LegalKeySizes[0].SkipSize;
    
    if (keySize > maxSize)
    {
     // Extract maximum size allowed
     mKey = mKey.Substring(0, maxSize / 8);
    }
    else if (keySize < maxSize)
    {
     // Set valid size
     int validSize = (keySize <= minSize)? minSize : 
          (keySize - keySize % skipSize) + skipSize;
     if (keySize < validSize)
     {
      // Pad the key with asterisk to make up the size
      mKey = mKey.PadRight(validSize / 8, '*');
     }
    }
   }
   PasswordDeriveBytes key = new PasswordDeriveBytes(mKey,
        ASCIIEncoding.ASCII.GetBytes(mSalt));
   return key.GetBytes(mKey.Length);
  }

  public virtual string Encrypt(string plainText)
  {
   byte[] plainByte = ASCIIEncoding.ASCII.GetBytes(plainText);
   byte[] keyByte = GetLegalKey();

   // Set private key
   mCryptoService.Key = keyByte;
   SetLegalIV();
   
   // Encryptor object
   ICryptoTransform cryptoTransform = mCryptoService.CreateEncryptor();
   
   // Memory stream object
   MemoryStream ms = new MemoryStream();

   // Crpto stream object
   CryptoStream cs = new CryptoStream(ms, cryptoTransform, 
        CryptoStreamMode.Write);

   // Write encrypted byte to memory stream
   cs.Write(plainByte, 0, plainByte.Length);
   cs.FlushFinalBlock();

   // Get the encrypted byte length
   byte[] cryptoByte = ms.ToArray();

   // Convert into base 64 to enable result to be used in Xml
   return Convert.ToBase64String(cryptoByte, 0, cryptoByte.GetLength(0));
  }
  
  public virtual string Decrypt(string cryptoText)
  {
   // Convert from base 64 string to bytes
   byte[] cryptoByte = Convert.FromBase64String(cryptoText);
   byte[] keyByte = GetLegalKey();

   // Set private key
   mCryptoService.Key = keyByte;
   SetLegalIV();

   // Decryptor object
   ICryptoTransform cryptoTransform = mCryptoService.CreateDecryptor();
   try
   {
    // Memory stream object
    MemoryStream ms = new MemoryStream(cryptoByte, 0, cryptoByte.Length);

    // Crpto stream object
    CryptoStream cs = new CryptoStream(ms, cryptoTransform, 
        CryptoStreamMode.Read);

    // Get the result from the Crypto stream
    StreamReader sr = new StreamReader(cs);
    return sr.ReadToEnd();
   }
   catch
   {
    return null;
   }
  }

  public string Key
  {
   get
   {
    return mKey;
   }
   set
   {
    mKey = value;
   }
  }

  public string Salt
  {
   // Salt value
   get
   {
    return mSalt;
   }
   set
   {
    mSalt = value;
   }
  }
  #endregion
 }
 #endregion

 #region Hash Class...
 
 /// <SUMMARY>CHashProtector is a password protection 
 /// one way encryption algorithm</SUMMARY>
 /// <PROGRAMMER>Chidi C. Ezeukwu</PROGRAMMER>
 /// <WRITTEN>May 16, 2004</WRITTEN>
 /// <UPDATED>Aug 07, 2004</UPDATED>
  
 public class Hash
 {
  #region Private member variables...

  private string mSalt;
  private HashAlgorithm mCryptoService;

  #endregion

  #region Public interfaces...

  public enum ServiceProviderEnum: int
  {
   // Supported algorithms
   SHA1, 
   SHA256, 
   SHA384, 
   SHA512, 
   MD5
  }

  public Hash()
  {
   // Default Hash algorithm
   mCryptoService = new SHA1Managed();
  }

  public Hash(ServiceProviderEnum serviceProvider)
  { 
   // Select hash algorithm
   switch(serviceProvider)
   {
    case ServiceProviderEnum.MD5:
     mCryptoService = new MD5CryptoServiceProvider();
     break;
    case ServiceProviderEnum.SHA1:
     mCryptoService = new SHA1Managed();
     break;
    case ServiceProviderEnum.SHA256:
     mCryptoService = new SHA256Managed();
     break;
    case ServiceProviderEnum.SHA384:
     mCryptoService = new SHA384Managed();
     break;
    case ServiceProviderEnum.SHA512:
     mCryptoService = new SHA512Managed();
     break;
   }
  }

  public Hash(string serviceProviderName)
  {
   try
   {
    // Set Hash algorithm
    mCryptoService = (HashAlgorithm)CryptoConfig.CreateFromName(
      serviceProviderName.ToUpper());
   }
   catch
   {
    throw;
   }
  }

  public virtual string Encrypt(string plainText)
  {
   byte[] cryptoByte =  mCryptoService.ComputeHash(
       ASCIIEncoding.ASCII.GetBytes(plainText + mSalt));
   
   // Convert into base 64 to enable result to be used in Xml
   return Convert.ToBase64String(cryptoByte, 0, cryptoByte.Length);
  }

  public string Salt
  {
   // Salt value
   get
   {
    return mSalt;
   }
   set
   {
    mSalt = value;
   }
  }
  #endregion
 }
 #endregion

}

I wish to express my sincere gratitude to Frank Fang; his selfless efforts inspired me to write the first edition of this article and code. I learned a lot from his few mistakes. I am also grateful to Furty for his comments that actually gave birth to the second edition. He wants me to follow best practices, and make the encrypted text harder to decode. Certainly, he is not a friend of hackers. :)

Finally, I can’t prove that there is no error in this code, that is impossible; I can only prove that a bug exists. However, I have found none at this time. Please test as much as you can and report as many errors as you can find. Thanks!

Points of Interest

Writing this code has helped me to understand more clearly the underlying concept of the .NET cryptographic APIs. As you may have observed, I did a simple arithmetic to get a valid size for any key you wish to use! If your key is less than minimum size, it will be filled up with asterisk(*), if it is more than maximum size, then it will be truncated to the maximum allowable size. Just try it out.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Canada Canada
I hold a masters degree in Applied Computer Science from the Vrije Universiteit Brussels (VUB), Belgium. I also hold a bachelors degree in Electronic Engineering.

I am also a Microsoft Certified Solution Developer (MCSD.NET) and Microsoft Certified Professional Developer with several years of experience in enterprise software systems design and development.

Comments and Discussions

 
QuestionIs a license required to use the attached code Pin
noob1000118-Sep-19 9:14
noob1000118-Sep-19 9:14 
QuestionLicense Query Pin
Member 1351967113-Nov-17 18:17
Member 1351967113-Nov-17 18:17 
QuestionLicensing question Pin
Bruno Vianna7-Mar-16 3:17
Bruno Vianna7-Mar-16 3:17 
GeneralReturn "Null" for 1st Decrypted value Pin
Member 440381320-Oct-08 9:01
Member 440381320-Oct-08 9:01 
NewsTwo other related encryption articles in CodeProject ... Pin
Tony Selke27-Sep-07 7:13
Tony Selke27-Sep-07 7:13 
You may also be interested in looking at the following, related Code Project articles:

Generic SymmetricAlgorithm Helper[^]
This is a generic helper class that exposes simplified Encrypt and Decrypt functionality for strings, byte arrays and streams for any SymmetricAlgorithm derivative (DES, RC2, Rijndael, TripleDES, etc.).

Making TripleDES Simple in VB.NET and C#[^]
This is a simple wrapper class that provides an easy interface for encrypting and decrypting byte arrays and strings using the 3DES algorithm.
GeneralGenerating Fixed-Length Hashes / Encryptions Pin
Tim McCurdy2-Aug-06 5:47
Tim McCurdy2-Aug-06 5:47 
GeneralRe: Generating Fixed-Length Hashes / Encryptions Pin
faisalsyed22-Sep-11 23:38
faisalsyed22-Sep-11 23:38 
GeneralSystem.FormatException: Invalid length for a Base-64 char array. Pin
vicms23-Jun-05 22:20
vicms23-Jun-05 22:20 
GeneralRe: System.FormatException: Invalid length for a Base-64 char array. Pin
DevGuruDude28-Nov-06 9:57
DevGuruDude28-Nov-06 9:57 
GeneralNeed similar solution. Will pay $150 Pin
thomastom2-Jun-05 13:32
thomastom2-Jun-05 13:32 
Questionis the key generation standard? Pin
taherz8-Jan-05 6:38
taherz8-Jan-05 6:38 
GeneralSupport for Unicode Pin
Jakob Røjel21-Dec-04 19:12
sussJakob Røjel21-Dec-04 19:12 
GeneralGetLegalIV Pin
Steven Campbell11-Aug-04 2:25
Steven Campbell11-Aug-04 2:25 
GeneralRe: GetLegalIV Pin
Furty11-Aug-04 17:47
Furty11-Aug-04 17:47 
GeneralRe: GetLegalIV Pin
Chidi Ezeukwu12-Aug-04 13:53
Chidi Ezeukwu12-Aug-04 13:53 
GeneralRe: GetLegalIV Pin
Steven Campbell13-Aug-04 3:07
Steven Campbell13-Aug-04 3:07 
GeneralRe: GetLegalIV Pin
Anonymous13-Aug-04 4:59
Anonymous13-Aug-04 4:59 
GeneralRe: GetLegalIV Pin
Steven Campbell27-Oct-04 16:33
Steven Campbell27-Oct-04 16:33 
Generalbyte[] GetLegalKey(string key) Pin
Furty30-May-04 15:37
Furty30-May-04 15:37 
GeneralRe: byte[] GetLegalKey(string key) Pin
Chidi Ezeukwu30-May-04 16:39
Chidi Ezeukwu30-May-04 16:39 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.