Make extension methods that encrypt and decrypt strings in C#

The .NET cryptography methods work most naturally with streams or byte arrays not strings. To make working with strings easier, this program defines extension methods that convert between byte arrays and strings containing hexadecimal values. Then when a program encrypts a string into an array of bytes, it can display the result as a hexadecimal string.

// Convert a byte array into a readable string of hexadecimal values.
public static string ToHex(this byte[] the_bytes)
{
return ToHex(the_bytes, false);
}
public static string ToHex(this byte[] the_bytes, bool add_spaces)
{
string result = "";
string separator = "";
if (add_spaces) separator = " ";
for (int i = 0; i < the_bytes.Length; i++)
{
result += the_bytes[i].ToString("x2") + separator;
}
return result;
}

// Convert a string containing 2-digit hexadecimal values into a byte array.
public static byte[] ToBytes(this string the_string)
{
List the_bytes = new List();
the_string = the_string.Replace(" ", "");

for (int i = 0; i < the_string.Length; i += 2)
{
the_bytes.Add(
byte.Parse(the_string.Substring(i, 2),
System.Globalization.NumberStyles.HexNumber));
}
return the_bytes.ToArray();
}

The real cryptographic work is done by the CryptBytes method.

// Encrypt or decrypt the data in in_bytes[] and return the result.
public static byte[] CryptBytes(string password, byte[] in_bytes, bool encrypt)
{
// Make an AES service provider.
AesCryptoServiceProvider aes_provider = new AesCryptoServiceProvider();

// Find a valid key size for this provider.
int key_size_bits = 0;
for (int i = 1024; i > 1; i--)
{
if (aes_provider.ValidKeySize(i))
{
key_size_bits = i;
break;
}
}
Debug.Assert(key_size_bits > 0);
Console.WriteLine("Key size: " + key_size_bits);

// Get the block size for this provider.
int block_size_bits = aes_provider.BlockSize;

// Generate the key and initialization vector.
byte[] key = null;
byte[] iv = null;
byte[] salt = { 0x0, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0xF1, 0xF0, 0xEE, 0x21, 0x22, 0x45 };
MakeKeyAndIV(password, salt, key_size_bits, block_size_bits, out key, out iv);

// Make the encryptor or decryptor.
ICryptoTransform crypto_transform;
if (encrypt)
{
crypto_transform = aes_provider.CreateEncryptor(key, iv);
}
else
{
crypto_transform = aes_provider.CreateDecryptor(key, iv);
}

// Create the output stream.
using (MemoryStream out_stream = new MemoryStream())
{
// Attach a crypto stream to the output stream.
using (CryptoStream crypto_stream = new CryptoStream(out_stream,
crypto_transform, CryptoStreamMode.Write))
{
// Write the bytes into the CryptoStream.
crypto_stream.Write(in_bytes, 0, in_bytes.Length);
try
{
crypto_stream.FlushFinalBlock();
}
catch (CryptographicException)
{
// Ignore this exception. The password is bad.
}
catch
{
// Re-throw this exception.
throw;
}

// return the result.
return out_stream.ToArray();
}
}
}

The basic idea is to make a cryptographic service provider and attach it to a stream. As you write into the stream, the provider automatically encrypts or decrypts the data. The details are in creating and initializing the provider.

The method creates a new AesCryptoServiceProvider to use the AES encryption method.

Next the program must make a key and initialization vector (IV) to initialize the service provider. It starts by finding a supported key size. It starts with a key size of 1,024 and reduces it until the provider's ValidKeySize method returns true. The key size you get will depend on things such as which version of Windows you are using.

Note: If you will encrypt and decrypt files on different computers, they must be able to use the same key size. You may need to set this value yourself if one computer can use a larger key than the other.

The program then calls the MakeKeyAndIV method described shortly to create a key and IV. The salt is an array of pseudo-random bytes that you initialize to make breaking the code with a dictionary attack harder. Pick your own set of values for this, don't use the values shown here.

The method then creates an encryptor or decryptor, depending on whether it must encrypt or decrypt the file.

The rest of the method looks messy but is straightforward. It makes an output MemoryStream, associates it with the encryptor/decryptor, and then writes into the stream.

The following code shows the MakeKeyAndIV method.

// Use the password to generate key bytes.
private static void MakeKeyAndIV(string password, byte[] salt, int key_size_bits, int block_size_bits, out byte[] key, out byte[] iv)
{
Rfc2898DeriveBytes derive_bytes = new Rfc2898DeriveBytes(password, salt, 1000);

key = derive_bytes.GetBytes(key_size_bits / 8);
iv = derive_bytes.GetBytes(block_size_bits / 8);
}

This code creates a new Rfc2898DeriveBytes object, passing its constructor your password, salt, and an iteration number. The object applies its operation the indicated number of times to make its result "more random." In this case, it applies a pseudo-random number generator based on the HMACSHA1 algorithm 1000 times to generate its bytes.

The method then uses the object's GetBytes methods to get the key and IV that the program needs to initialize the cryptographic service provider.

Very Important Note: Never store a password inside a program. If you do, then a clever attacker can break open your program, read the password, and decrypt whatever the program wants to keep secret. This is particularly easy for .NET programs where it's relatively easy to read the program's IL code. A much better approach is to make the user enter the password at run time.

Also note that the password must match exactly to decode a file. If the password is off by even a single character, the result will be complete gibberish.

   

 

What did you think of this article?




Trackbacks
  • No trackbacks exist for this post.
Comments
  • No comments exist for this post.
Leave a comment

Submitted comments are subject to moderation before being displayed.

 Name

 Email (will not be published)

 Website

Your comment is 0 characters limited to 3000 characters.