.Net Cryptography (Encryption / Decryption)

There are two techniques for encrypting data: symmetric encryption (secret key encryption) and asymmetric encryption (public key encryption.)

Symmetric Encryption

Symmetric encryption is the oldest and best-known technique. A secret key, which can be a number, a word, or just a string of random letters, is applied to the text of a message to change the content in a particular way. This might be as simple as shifting each letter by a number of places in the alphabet. As long as both sender and recipient know the secret key, they can encrypt and decrypt all messages that use this key.

Asymmetric Encryption

The problem with secret keys is exchanging them over the Internet or a large network while preventing them from falling into the wrong hands. Anyone who knows the secret key can decrypt the message. One answer is asymmetric encryption, in which there are two related keys–a key pair. A public key is made freely available to anyone who might want to send you a message. A second, private key is kept secret, so that only you know it.

Any message (text, binary files, or documents) that are encrypted by using the public key can only be decrypted by applying the same algorithm, but by using the matching private key. Any message that is encrypted by using the private key can only be decrypted by using the matching public key.

This means that you do not have to worry about passing public keys over the Internet (the keys are supposed to be public). A problem with asymmetric encryption, however, is that it is slower than symmetric encryption. It requires far more processing power to both encrypt and decrypt the content of the message

 

Lets see how both the techniques can be implemented using C#.Net 4.0

//The CryptoBase class represents the base class for both kind of techniques. the code is mentioned below

 

public abstract class CryptoBase:IDisposable
{
public CryptoBase(IDisposable provider)
{
this.Provider = provider;
}

protected IDisposable Provider { get; private set; }

/// <summary>
/// Encryption the source stream and save result to target stream
/// </summary>
/// <param name="source"></param>
/// <param name="target"></param>
public abstract void Encrypt(System.IO.Stream source, System.IO.Stream target);

/// <summary>
/// Decrypt the source stream and save result to target stream
/// </summary>
/// <param name="source"></param>
/// <param name="target"></param>
public abstract void Decrypt(System.IO.Stream source, System.IO.Stream target);

protected abstract void OnDisposeProvider();

/// <summary>
/// Copy the source stream to target stream and transform the bytes (using function deligate) before copying onto target stream
/// </summary>
/// <param name="source">Source stream</param>
/// <param name="target">Target stream</param>
/// <param name="BytesProcessor">Function deligate to process the data beforw it get copied to target stream.</param>
protected void CopyStream(Stream source, Stream target, Func<byte[], byte[]> BytesProcessor)
{
const int bufSize = 1024;
byte[] buf = new byte[bufSize];
int bytesRead = 0;
while ((bytesRead = source.Read(buf, 0, bufSize)) > 0)
{
//extract the actual buffer using bytesRead
byte[] buffactual = new byte[bytesRead];

//Copt the data to buffer
Array.Copy(buf, buffactual, bytesRead);

//Call the bytes processor to process the bytes before we write it to target
byte[] processed = BytesProcessor(buffactual);

//Write the new data to target.
target.Write(processed, 0, processed.Length);
}
}

/// <summary>
/// Copy the source tream to target stream.
/// </summary>
/// <param name="source">Source stream</param>
/// <param name="target">Target stream</param>
protected void CopyStream(Stream source, Stream target)
{
const int bufSize = 1024;
byte[] buf = new byte[bufSize];
int bytesRead = 0;
while ((bytesRead = source.Read(buf, 0, bufSize)) > 0)
target.Write(buf, 0, bytesRead);
}

private bool isDisposing = false;
~CryptoBase()
{
this.Dispose();
}

private void Dispose()
{
if (!isDisposing && Provider != null)
{
OnDisposeProvider();
isDisposing = true;
Provider.Dispose();
Provider = null;
}
}

void IDisposable.Dispose()
{
this.Dispose();
}
}

Now lets see the Symmetric implementation based on RijndaelManaged provider

/// <summary>
/// Symmetric encryption/decryption class based on RijndaelManaged provider
/// </summary>
public sealed class Symmetric : CryptoBase
{

private readonly byte[] passcode;
private readonly byte[] vector;

/// <summary>
/// Initilize the Symmetric Encryption from Vector, PassCode and Salt
/// </summary>
/// <param name="Vector">Randomly generated 16 bit array (16 means - 128 bit AES encryption)</param>
/// <param name="PassCode">Random passcode bytes. passcode size should be from (5 to 15)*8 bytes</param>
/// <param name="Salt"></param>
public Symmetric(byte[] Vector, byte[] PassCode,string Salt):base(new RijndaelManaged())
{
MD5CryptoServiceProvider md5Crypt = new MD5CryptoServiceProvider();

//Construct the derived password, hash name should be SHA1 or MD5
PasswordDeriveBytes password = new PasswordDeriveBytes(PassCode, md5Crypt.ComputeHash(UnicodeEncoding.ASCII.GetBytes(Salt)), "SHA1", 2);
//C# provides 4 different symmetric crypto algorithms:
//RijndaelManaged, DESCryptoServiceProvider, RC2CryptoServiceProvider, and TripleDESCryptoServiceProvider.

//Rijndael is the same as AES (Advanced Encryption Standard - approved by NSA, very strong)
//but with more choice about the size of your key.

SymmetricAlgorithm symmetricProvider = base.Provider as RijndaelManaged;
symmetricProvider.Mode = CipherMode.CBC;

//Set the passcode and vector
passcode = password.GetBytes(32);
vector = Vector;
}
public override void Encrypt(Stream source, Stream target)
{
SymmetricAlgorithm symmetricProvider = base.Provider as RijndaelManaged;

//Create encryptor from passcode and vector
using (ICryptoTransform CryptoTransformer = symmetricProvider.CreateEncryptor(passcode, vector))
{
//Encrypt the stream
using (CryptoStream cryptostream = new CryptoStream(target, CryptoTransformer, CryptoStreamMode.Write))
{
base.CopyStream(source, cryptostream);
}
}

}

public override void Decrypt(Stream source, Stream target)
{
SymmetricAlgorithm symmetricProvider = base.Provider as RijndaelManaged;

//Create decryptor from passcode and vector
using (ICryptoTransform CryptoTransformer = symmetricProvider.CreateDecryptor(passcode, vector))
{
//Decrypt the stream
using (CryptoStream cryptostream = new CryptoStream(source, CryptoTransformer, CryptoStreamMode.Read))
{
CopyStream(cryptostream, target);
}
}
}

protected override void OnDisposeProvider()
{
SymmetricAlgorithm symmetricProvider = base.Provider as RijndaelManaged;
symmetricProvider.Clear();
}
}

and the asymmetric implementation as well

 
/// <summary>
/// Asymmetric (RSA) encryption/decryption class
/// </summary>
internal sealed class Asymmetric : CryptoBase
{

/* Note: the RSACryptoServiceProvider reverses the order of encrypted bytes
* after encryption and before decryption. If you do not require compatibility
* with Microsoft Cryptographic API (CAPI) and/or other vendors
* Set CAPICompatibility = False;
*/

private readonly bool CAPICompatibility = true;

/// <summary>
/// Create the provider from RSACryptoService Provider
/// </summary>
/// <param name="provider"></param>
public Asymmetric(RSACryptoServiceProvider provider):base(provider)
{

}

/// <summary>
/// Encrypt the data using RSACryptoServiceProvider
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
private byte[] Encrypt(byte[] bytes)
{
RSACryptoServiceProvider Provider = base.Provider as RSACryptoServiceProvider;

int keySize = Provider.KeySize / 8;

// The hash function in use by the .NET RSACryptoServiceProvider here is SHA1
int maxLength = (keySize) - 2 - (2 * SHA1.Create().ComputeHash(bytes).Length);

int dataLength = bytes.Length;
//Compute the iterations based on data length
int iterations = dataLength / maxLength;
List<byte> result = new List<byte>();
//loop through data and encrypt the bytes
for (int i = 0; i <= iterations; i++)
{
byte[] tempBytes = new byte[(dataLength - maxLength * i > maxLength) ? maxLength : dataLength - maxLength * i];
Buffer.BlockCopy(bytes, maxLength * i, tempBytes, 0, tempBytes.Length);
byte[] encryptedBytes = Provider.Encrypt(tempBytes, false);
//The microsoft crypto api reverse the bytes after encryption
//if CAPICompatibility is required the reverse the data
if (CAPICompatibility)
Array.Reverse(encryptedBytes);
result.AddRange(encryptedBytes);
}
//return the encrypted data
return result.ToArray();
}

/// <summary>
/// Decrypt the byte array based on RSACryptoServiceProvider
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
private byte[] Decrypt(byte[] bytes)
{
RSACryptoServiceProvider Provider = base.Provider as RSACryptoServiceProvider;

//Compute the key size
int keySize = Provider.KeySize / 8;

int maxLength = keySize;

int dataLength = bytes.Length;
//Compute the iterations based on data length
int iterations = dataLength / maxLength;

List<byte> result = new List<byte>();
for (int i = 0; i <= iterations; i++)
{
byte[] tempBytes = new byte[(dataLength - maxLength * i > maxLength) ? maxLength : dataLength - maxLength * i];
if (tempBytes.Length > 0)
{
Buffer.BlockCopy(bytes, maxLength * i, tempBytes, 0, tempBytes.Length);
//The microsoft crypto api requires the reversed bytes before decryption
//if CAPICompatibility is set then reverse the data
if (CAPICompatibility)
Array.Reverse(tempBytes);
//Decrypt the data
byte[] encryptedBytes = Provider.Decrypt(tempBytes, false);
result.AddRange(encryptedBytes);
}
}
//return the decrypted data
return result.ToArray();
}
public override void Encrypt(Stream source, Stream target)
{
//Call the Copy Stream function and pass the refrence to Encrypt function as byte processor
//The byte processor will be called before writting final data to output stream
CopyStream(source, target, Encrypt);
}

public override void Decrypt(Stream source, Stream target)
{
//Call the Copy Stream function and pass the refrence to Decrypt function as byte processor
//The byte processor will be called before writting final data to output stream
CopyStream(source, target, Decrypt);
}

protected override void OnDisposeProvider()
{
RSACryptoServiceProvider symmetricProvider = base.Provider as RSACryptoServiceProvider;
symmetricProvider.Clear();
}

}

And at last the usage function will look like

class Program
{
private static Random random = new Random();

static void Main(string[] args)
{
//Asymmetric Encryption/Decryption usage
//Get the certificate

X509Certificate2 certificate = null;// = Get the certificate from store (Left for ideveloper/implementor )

//User the private key to decrypt the data
using (CryptoBase asymmetric = new Asymmetric((RSACryptoServiceProvider)certificate.PrivateKey))
{

//asymmetric.Decrypt(source ,target);

}

//User the public key to encrypt the data
using (CryptoBase asymmetric = new Asymmetric((RSACryptoServiceProvider)certificate.PublicKey.Key))
{
//asymmetric.Encrypt(source ,target);
}
//Symmetric Encryption/Decryption usage
//Get the certificate

byte[] vector = GenerateRandomBytes(16);
byte[] passcode = GenerateRandomBytes(10);
string salt = "ChooseSalt";

//User the private key to decrypt the data
using (CryptoBase asymmetric = new Symmetric(vector,passcode,salt))
{

//asymmetric.Decrypt(source ,target);

}

//User the public key to encrypt the data
using (CryptoBase asymmetric = new Symmetric(vector, passcode, salt))
{
//asymmetric.Encrypt(source ,target);
}

}

public static byte[] GenerateRandomBytes(int Size)
{
byte[] buffer = new byte[Size];
random.NextBytes(buffer);
return buffer;
}
}

The initialization of certificate from data store is not implemented in above sample.

If you have huge data and Asymmetric encryption is doesn’t meet you performance requirements, you could mix both the techniques. For eg. Decrypt the Symmetric keys using Asymmetric technique and write it on header of the data. Decrypt the rest of data segment with fast Symmetric technique.To make it more stronger you might develop custom decryption technique which decrypt most of data with symmetric but also decrypt some segment of data with Asymmetric technique. Use certificates with key size of 1024 bit or higher for more security.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s