Click here to Skip to main content
Click here to Skip to main content

Encryption and Decryption on the .NET Framework

, 6 Aug 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
An Article that Illustrates some Simple Ways to use the System.Security.Cryptography namespace

Preface

The purpose of this article is to explain how to use encryption and decryption using C# on the .NET Framework. Data is most vulnerable when it is stored persistently or transferred across a network. Although you can use permission demands to control access to your application and ACLs to protect data, an attacker with access to the hard disk or network infrastructure can bypass software security and either extract private information from the data or modify the data. In .NET, you can use cryptography to protect the privacy and integrity of your data that your application stores or transfers. The .NET Framework provides classes for several different types of cryptography, including symmetric and asymmetric encryption, hashing, and digital signatures. With symmetric encryption, the algorithm, or cipher, is reversible, and the behavior of the algorithm depends on the length of the key. The same key that is used to encrypt the data is used to decrypt the data. While this seems risky, there is less overhead with a symmetric algorithm. An asymmetric algorithm uses one key to encrypt the data and another key to decrypt the data. An md5 hash is not mathematically reversible: it is a one way function that depends on four additive constants. Hashes like MD5 and SHA-1 are useful to have files, and then compare the hashes of those files. If an attacker wishes to crack a cipher block, he probably weighs the cost of the time, effort, and resources put into his attack against the value of the data that he seeks. Symmetric algorithms with long key lengths take longer to crack than other algorithms. The following code illustrates how to encrypt a file and are then used to decrypt that file. The file that I have chosen is “autoexec.NT”, which resides in the C:\windows\system32 directory:

using System;
using System.IO;
using System.Text;
using System.Security.Cryptography;
public sealed class Program {
public static void Main() {
string inFileName = @"C:\windows\system32\autoexec.NT";
string outFileName = @"C:\windows\system32\autoexec.NT.enc";
string password = "passwd";

// Create the password key
byte[]  saltValueBytes = Encoding.ASCII.GetBytes("This is my salt");
Rfc2898DeriveBytes passwordKey = new Rfc2898DeriveBytes(password, saltValueBytes);
// Create the algorithm and specify the key and IV
RijndaelManaged alg = new RijndaelManaged();
alg.Key = passwordKey.GetBytes(alg.KeySize/8);
alg.IV = passwordKey.GetBytes(alg.BlockSize/8);
// Read the unencrypted file into fileData
FileStream inFile = new FileStream(inFileName, FileMode.Open, FileAccess.Read);
byte[]  fileData = new byte[inFile.Length];
inFile.Read(fileData, 0, (int)inFile.Length);
// Create the ICryptoTransform and CryptoStream object
ICryptoTransform encryptor = alg.CreateEncryptor();
FileStream outFile = new FileStream(outFileName, FileMode.OpenOrCreate, FileAccess.Write);
CryptoStream encryptStream = new CryptoStream(outFile, encryptor, CryptoStreamMode.Write);

// Write the contents to the CryptoStream
encryptStream.Write(fileData, 0, fileData.Length);
// Close the file handles
encryptStream.Close();
inFile.Close();
outFile.Close();
  }
}

Compiling this code on the console results in Encrypt.exe executable. Because we chose the autoexec.NT file that resides in the ../system32 directory, we can type on the console screen:

’notepad C:\Windows\System32\Autoexec.NT.enc’ 

The result of the file is shown below:

Click to enlarge image

How To Decrypt the File

using System;
using System.IO;
using System.Text;
using System.Security.Cryptography;
public sealed class Program {
public static void Main() {

string inFileName = @"C:\windows\system32\autoexec.NT.enc";
string outFileName = @"C:\windows\system32\autoexec.NT.enc.dec";
string password = "passwd";

// Create the password key
byte[]  saltValueBytes = Encoding.ASCII.GetBytes("This is my salt");
Rfc2898DeriveBytes passwordKey = new Rfc2898DeriveBytes(password, saltValueBytes);

// Create the algorithm and specify the key and IV
RijndaelManaged alg = new RijndaelManaged();
alg.Key = passwordKey.GetBytes(alg.KeySize/8);
alg.IV = passwordKey.GetBytes(alg.BlockSize/8);

// Read the encrypted file into fileData
ICryptoTransform decryptor = alg.CreateDecryptor();
FileStream inFile = new FileStream(inFileName, FileMode.Open, FileAccess.Read);
CryptoStream decryptStream = new CryptoStream(inFile, decryptor, CryptoStreamMode.Read);
byte[]  fileData = new byte[inFile.Length];
decryptStream.Read(fileData, 0, (int)inFile.Length);

// Write the contents of the unencrypted file
FileStream outFile = new FileStream(outFileName, FileMode.OpenOrCreate, FileAccess.Write);
outFile.Write(fileData, 0, fileData.Length);

// Close the file handles
decryptStream.Close();
inFile.Close();
outFile.Close();
  }
}

How It Works

All symmetric algorithm classes are derived from the System.Security.Cryptography.SymmetricAlgorithm base class and share the following properties:

  • BlockSize gets or sets the block size of the cryptographic operation in bits. The block size is the number in bits that the algorithm processes at a single time.
  • FeedBackSize gets or sets the feedback size of the cryptographic operation in bits. This property is normally (safely) ignored.
  • IV gets or sets the initialization vector for the symmetric algorithm. Like the Key property, both the encryptor and the decryptor must specify the same value.
  • Key gets or sets the secret key for the symmetric algorithm. Keys are automatically generated if you have not specifically defined them. After encryption, you must store this value and transfer it to the decryptor. During decryption, you must specify the same key used during encryption.
  • KeySize gets or sets the size of the secret key used by the symmetric algorithm in bits. When you create a symmetric algorithm object, the runtime will choose the largest key size supported by the platform.

Additionally, the symmetric algorithm classes each share the following methods:

  • CreateDecryptor: To decrypt messages, you must create a symmetric algorithm object and call this method to create an ICryptoTransform object that a CryptoStream object can use to decrypt the Stream.
  • CreateEncryptor: Creates a symmetric encryptor object used by CryptoStream objects to encrypt a stream.
  • GenerateIV : Generates a random IV to be used for the algorithm.
  • GenerateKey: Generates a random key to be used by the algorithm.
  • ValidKeySize: Determines whether the specified key size is valid for the current algorithm.

Creating symmetric keys based on a password requires different values to be synchronized between the encryptor and the decryptor:

  • The password
  • The salt value
  • The number of iterations used to generate the key (or you can accept the default)

The simplest way to specify these values is to pass them to the Rfc2898DeriveBytes constructor. After initialization, you can retrieve a key by calling the Rfc2898DeriveBytes.GetBytes method. GetBytes accepts the number of bytes to return as an integer. When deriving a key, determine the length based on the number of bits required by the algorithm object’s KeySize and BlockSize properties. Note that both KeySize and BlockSize are defined as a number of bits, whereas the Rfc2898DeriveBytes.GetBytes method requires a number of bytes. You must divide the number of bits required for the key by 8 to determine the number of bytes required. Besides the key, the encryption algorithm must also have the same IV specified at both the encryptor and the decryptor. Below is the file that decrypts the autoexec.NT.enc file.

Validating Data Integrity with Hashes

Another important use of cryptography is protecting data integrity by using hashes. A hash is a checksum that is unique to a specific file or piece of data. You can use a hash value to verify that a file has not been modified after the hash was generated. Unlike encryption, you cannot drive the original data from the hash, even if the original data is very small: creating a hash is a one-way operation. Hashes are used to enable passwords to be verified without storing the password itself. After the hash of the password has been stored, the application can verify the password by calculating the hash of the provided password and comparing it with the stored hash. The two hash values will match if the user has provided the same password.

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
public sealed class Program {
public static void Main(string[]  args) {

// create the hash algorithm object
MD5 myHash = new MD5CryptoServiceProvider();

// store the data to be hashed in a byte array
FileStream file = new FileStream(args[0], FileMode.Open, FileAccess.Read);
BinaryReader reader = new BinaryReader(file);

//call the HashAlgorithm.Compute method
myHash.ComputeHash(reader.ReadBytes((int)file.Length));

//retrieve the HashAlgorithm.Hash byte array
Console.WriteLine(Convert.ToBase64String(myHash.Hash));
     }
}

Signing Files

A digital signature is a value that can be appended to electronic data to prove that it was created by someone else who possesses a specific private key. Digital signatures provide separate methods for signing and verifying data, whereas hashes do not provide separate methods for verification. The reason that hash algorithms do not need a separate method for signing and verifying is that the recipient of the sent data can easily re-create the hash and then compare the hash he or she generated with the hash the sender provided. However, digital signatures use asymmetric encryption. Therefore, the recipient cannot regenerate the signature without the sender’s private key, although the signature can be verified by using the sender of the message’s public key. The VerifyData and VerifyHash methods use the sender’s public key; the SignData and SignHash use the sender’s private key. Here is the code written in the C# language that executes on the runtime of the .NET Framework:

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
public sealed class Program {

public static void Main(string[]  args) {

// create the digital signature algorithm object
DSACryptoServiceProvider signer = new DSACryptoServiceProvider();

// store the data to be signed in a byte array
FileStream file = new FileStream(args[0], FileMode.Open, FileAccess.Read);
BinaryReader reader = new BinaryReader(file);
byte[]  data = reader.ReadBytes((int)file.Length);

// call the SignData method and store the signature
byte[]  signature = signer.SignData(data);

//export the public key
string publicKey = signer.ToXmlString(false);
Console.WriteLine("Signature:  "  +  Convert.ToBase64String(signature));
reader.Close();
file.Close();

// NOW, verifying step one: create the digital signature algorithm object
DSACryptoServiceProvider verifier = new DSACryptoServiceProvider();

// verifying step 2: imports the signature and the key
verifier.FromXmlString(publicKey);

// verifying step 3: store the data to be verified in a byte array
FileStream file2 = new FileStream(args[0], FileMode.Open, FileAccess.Read);
BinaryReader reader2 = new BinaryReader(file2);
byte[]  data2 = reader2.ReadBytes((int)file2.Length);

// call the VerifyData method
if ( verifier.VerifyData(data2, signature))
  Console.WriteLine("Signature verified");
  else
   Console.WriteLine("Signature NOT verified");
   reader2.Close();
   file2.Close();
   }
 }

The previous example uses the DSACryptoServiceProvider class, but you could also use the RSACryptoServiceProvider class for digital signatures. RSACryptoServiceProvider usage is similar, but requires a hash algorithm object for both the SignData and VerifyData methods.

History

  • 7th August, 2009: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

logicchild
Other Pref. Trust
United States United States
I started electronics training at age 33. I began studying microprocessor technology in an RF communications oriented program. I am 43 years old now. I have studied C code, opcode (mainly x86 and AT+T) for around 3 years in order to learn how to recognize viral code and the use of procedural languages. I am currently learning C# and the other virtual runtime system languages. I guess I started with the egg rather than the chicken. My past work would indicate that my primary strength is in applied mathematics.

Comments and Discussions

 
GeneralCrypted/Encrypted file doesn`t match Pinmemberjohn.bermud22-Aug-09 14:59 
GeneralRe: Crypted/Encrypted file doesn`t match [modified] Pinmentorlogicchild22-Aug-09 16:32 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.141223.1 | Last Updated 7 Aug 2009
Article Copyright 2009 by logicchild
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid