Sunday, February 20, 2005
I just want to encrypt this simple line of text...
One of the many cool features in the .Net framework is the System.Security.Cryptography namespace. The Cryptography namespace and it's constituent classes are a bit daunting to many newcomers because there is no simple method to encrypt a string of text, and then decrypt the result and finally return the original string of text.It isn't uncommon to see questions in the MSDN Newsgroups asking for "a simple means of encrypting and decrypting a string." Encryption is much more involved than that. A successful encryption program has to include methods for, among many other things, password/key generation, password/key sharing, password/key storage, and password/key transportation. Those are all topics for other articles and beyond the scope of this introductory article.
In the most basic form of encryption application, along with the issues listed above, there must be a source of data to be encrypted and some need to transfer or store that data somewhere else. In other words, encryption generally means you're moving data from one storage location to another, whether it is to a new folder on your hard disk or between computers at opposite poles of the planet.
Because of that transitory nature of encrypted data, Microsoft has built their encryption classes around the Stream class. Streams are used for moving data - for saving data on your hard drive, for moving data on the Internet or other networks, or even moving data within your application. The result is that encrypting data requires creating a stream representing your data, in the form of a FileStream, MemoryStream, or other form of stream. Because streams easily take as input the output of other stream-based classes, they are ideal for manipulations in transit such as encryption and decryption.
If you inject a CryptoStream into the path of data moving from a MemoryStream in your application as it travels toward a FileStream to save it on your disk, the result is that the data is stored in encrypted form. Decrypting is basically the opposite - a FileStream retrieves encrypted data from your hard disk and sends the data into a CryptoStream where the data is decrypted. The decrypted data then may go into another stream, such as a MemoryStream where it can be extracted into its original format, for instance a collection of bytes or a string.
The code below demonstrates many of these principles, in the most basic of forms, as implemented in the Cryptography namespace. It encrypts a user supplied string using a user supplied password and then decrypts the result to return the original user string as the final result. It's not very useful by itself, but can serve as an introduction to the encryption/decryption capabilities of the Cryptrography namespace. The two methods provided here, EncryptText and DecryptText, can be used as the foundation for many more advanced or robust encryption schemes.
You can use techniques such as Convert.ToBase64String() method to pass the encrypted result around the Internet in Get/Post requests or in text emails. You can build on the techniques described here and in my article Encrypt and save the contents of your application's RichTextBox to learn how to save encrypted data to your hard disk.
Here's the code for the introductory Cryptography sample:
The code below is modified from how I originally posted it. I have included some helpful hints from Jon Skeet by using UTF-8 rather than ASCII encoding and a more robust stream reading model from his article at http://www.yoda.arachsys.com/csharp/readbinary.html.
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
namespace EncryptDemo
{
/// <summary>
/// Demonstrates encrypting and then decrypting a string using
/// Microsofot's System.Security.Cryptography classes.
/// </summary>
class EncryptDemo
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
Console.WriteLine("Enter a string of text to encrypt: " );
string plainText = Console.ReadLine();
Console.WriteLine("Enter the password: " );
string password = Console.ReadLine();
// Encrypt the input text
byte[] msgBytes = EncryptText(plainText, password);
// Display the encryption results
Console.WriteLine("\nEncrypted text with initialization vector prefixed:");
Console.WriteLine(System.Text.Encoding.ASCII.GetString(msgBytes));
// Decrypt the encrypted copy of the text
string decryptedText = DecryptText(msgBytes, password);
// Display the decryption results
Console.WriteLine("\nDecrypted text: ");
Console.WriteLine(decryptedText);
Console.WriteLine("\nPress Enter to quit.");
Console.ReadLine();
}
private static byte[] EncryptText(string plainText, string password)
{
RijndaelManaged rijndael = new RijndaelManaged();
// ASCIIEncoding textConverter = new ASCIIEncoding();
byte[] encrypted;
byte[] plainBytes;
// Get a 256 bit hash from the password string
byte[] pwBytes = Encoding.UTF8.GetBytes(password);
SHA256Managed sha = new SHA256Managed();
// Set the encryption key to the password hash
rijndael.Key = sha.ComputeHash(pwBytes);
//Create a new initialization vector.
rijndael.GenerateIV();
//Get an encryptor.
ICryptoTransform thisEncryptor = rijndael.CreateEncryptor(rijndael.Key, rijndael.IV);
//Encrypt the data.
MemoryStream msEncrypt = new MemoryStream();
CryptoStream csEncrypt = new CryptoStream(msEncrypt, thisEncryptor, CryptoStreamMode.Write);
//Convert the data to a byte array.
plainBytes = Encoding.UTF8.GetBytes(plainText);
//Write all data to the crypto stream and flush it.
csEncrypt.Write(plainBytes, 0, plainBytes.Length);
csEncrypt.FlushFinalBlock();
// Get encrypted array of bytes, add the IV at the beginning
encrypted = msEncrypt.ToArray();
byte[] cryptBytes = new byte[encrypted.Length + 16];
rijndael.IV.CopyTo(cryptBytes,0);
encrypted.CopyTo(cryptBytes,16);
return cryptBytes;
}
private static string DecryptText(byte[] cryptText, string password)
{
RijndaelManaged rijndael = new RijndaelManaged();
// ASCIIEncoding textConverter = new ASCIIEncoding();
// Create an array to hold the initialization vector (IV)
byte[] IV = new byte[16];
// Create an array to hold the encrypted text
byte[] cryptBytes = new byte[cryptText.Length - 16];
// Extract the IV from the input byte array.
Array.Copy(cryptText, 0, IV, 0, 16);
// Extract the encrypted message from the input byte array.
Array.Copy(cryptText, 16, cryptBytes, 0, cryptBytes.Length);
// Get a 256 bit hash from the password string
byte[] pwBytes = Encoding.UTF8.GetBytes(password);
SHA256Managed sha = new SHA256Managed();
// Set the encryption key to the password hash
rijndael.Key = sha.ComputeHash(pwBytes);
//Get a decryptor that uses the same key and IV as the encryptor.
ICryptoTransform decryptor = rijndael.CreateDecryptor(rijndael.Key, IV);
//Now decrypt the previously encrypted message using the decryptor
// obtained in the above step.
MemoryStream msDecrypt = new MemoryStream(cryptBytes);
CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read);
byte[] plainBytes = new byte[cryptBytes.Length];
//Read the data out of the crypto stream.
plainBytes = ReadFully(csDecrypt, cryptBytes.Length);
//Convert the byte array back into a string.
string plainText = Encoding.UTF8.GetString(plainBytes); //textConverter.GetString(plainBytes);
return plainText;
}
// the ReadFully method is from Jon Skeet at
// http://www.yoda.arachsys.com/csharp/readbinary.html
/// <summary>
/// Reads data from a stream until the end is reached. The
/// data is returned as a byte array. An IOException is
/// thrown if any of the underlying IO calls fail.
/// </summary>
/// <param name="stream">The stream to read data from</param>
/// <param name="initialLength">The initial buffer length</param>
public static byte[] ReadFully (Stream stream, int initialLength)
{
// If we've been passed an unhelpful initial length, just
// use 32K.
if (initialLength < 1)
{
initialLength = 32768;
}
byte[] buffer = new byte[initialLength];
int read=0;
int chunk;
while ( (chunk = stream.Read(buffer, read, buffer.Length-read)) > 0)
{
read += chunk;
// If we've reached the end of our buffer, check to see if there's
// any more information
if (read == buffer.Length)
{
int nextByte = stream.ReadByte();
// End of stream? If so, we're done
if (nextByte==-1)
{
return buffer;
}
// Nope. Resize the buffer, put in the byte we've just
// read, and continue
byte[] newBuffer = new byte[buffer.Length*2];
Array.Copy(buffer, newBuffer, buffer.Length);
newBuffer[read]=(byte)nextByte;
buffer = newBuffer;
read++;
}
}
// Buffer is now too big. Shrink it.
byte[] ret = new byte[read];
Array.Copy(buffer, ret, read);
return ret;
}
}
}
Comments:
<< Home
Trying to convert the byte array to a string and then decrypting it causes problem in .Net 2/VS2005. Looking at this blog: http://blogs.msdn.com/shawnfa/archive/2005/11/10/491431.aspx
it uses Base64 encoding and works very nicely.
it uses Base64 encoding and works very nicely.
Using Base64 is exactly the thing to do if you want the encrypted (or any other byte array) bytes stored as a string. I do it in my production app. This sample just didn't go that far.
Post a Comment
<< Home


