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:
- Greater than or equal to minimum size for a chosen cryptographic algorithm.
- Equal to a valid size for a chosen cryptographic algorithm.
- 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:
using System;
using CodeProject.Chidi.Cryptography;
namespace CodeProject.Chidi.TestConsole
{
class clsTest
{
[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:
using System;
using System.IO;
using System.Text;
using System.Security.Cryptography;
namespace CodeProject.Chidi.Cryptography
{
#region Symmetric cryptography class...
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()
{
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
{
Rijndael,
RC2,
DES,
TripleDES
}
public SymCryptography()
{
mCryptoService = new RijndaelManaged();
mCryptoService.Mode = CipherMode.CBC;
mAlgorithm = ServiceProviderEnum.Rijndael;
}
public SymCryptography(ServiceProviderEnum serviceProvider)
{
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
{
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;
}
mCryptoService = (SymmetricAlgorithm)
CryptoConfig.CreateFromName(serviceProviderName);
mCryptoService.Mode = CipherMode.CBC;
}
catch
{
throw;
}
}
public virtual byte[] GetLegalKey()
{
if (mCryptoService.LegalKeySizes.Length > 0)
{
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)
{
mKey = mKey.Substring(0, maxSize / 8);
}
else if (keySize < maxSize)
{
int validSize = (keySize <= minSize)? minSize :
(keySize - keySize % skipSize) + skipSize;
if (keySize < validSize)
{
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();
mCryptoService.Key = keyByte;
SetLegalIV();
ICryptoTransform cryptoTransform = mCryptoService.CreateEncryptor();
MemoryStream ms = new MemoryStream();
CryptoStream cs = new CryptoStream(ms, cryptoTransform,
CryptoStreamMode.Write);
cs.Write(plainByte, 0, plainByte.Length);
cs.FlushFinalBlock();
byte[] cryptoByte = ms.ToArray();
return Convert.ToBase64String(cryptoByte, 0, cryptoByte.GetLength(0));
}
public virtual string Decrypt(string cryptoText)
{
byte[] cryptoByte = Convert.FromBase64String(cryptoText);
byte[] keyByte = GetLegalKey();
mCryptoService.Key = keyByte;
SetLegalIV();
ICryptoTransform cryptoTransform = mCryptoService.CreateDecryptor();
try
{
MemoryStream ms = new MemoryStream(cryptoByte, 0, cryptoByte.Length);
CryptoStream cs = new CryptoStream(ms, cryptoTransform,
CryptoStreamMode.Read);
StreamReader sr = new StreamReader(cs);
return sr.ReadToEnd();
}
catch
{
return null;
}
}
public string Key
{
get
{
return mKey;
}
set
{
mKey = value;
}
}
public string Salt
{
get
{
return mSalt;
}
set
{
mSalt = value;
}
}
#endregion
}
#endregion
#region Hash Class...
public class Hash
{
#region Private member variables...
private string mSalt;
private HashAlgorithm mCryptoService;
#endregion
#region Public interfaces...
public enum ServiceProviderEnum: int
{
SHA1,
SHA256,
SHA384,
SHA512,
MD5
}
public Hash()
{
mCryptoService = new SHA1Managed();
}
public Hash(ServiceProviderEnum serviceProvider)
{
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
{
mCryptoService = (HashAlgorithm)CryptoConfig.CreateFromName(
serviceProviderName.ToUpper());
}
catch
{
throw;
}
}
public virtual string Encrypt(string plainText)
{
byte[] cryptoByte = mCryptoService.ComputeHash(
ASCIIEncoding.ASCII.GetBytes(plainText + mSalt));
return Convert.ToBase64String(cryptoByte, 0, cryptoByte.Length);
}
public string Salt
{
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.