[C# Helper]
Index Books FAQ Contact About Rod
[Beginning Database Design Solutions, Second Edition]

[Beginning Software Engineering, Second Edition]

[Essential Algorithms, Second Edition]

[The Modern C# Challenge]

[WPF 3d, Three-Dimensional Graphics with WPF and C#]

[The C# Helper Top 100]

[Interview Puzzles Dissected]

[C# 24-Hour Trainer]

[C# 5.0 Programmer's Reference]

[MCSD Certification Toolkit (Exam 70-483): Programming in C#]

Title: Use the .NET cryptography library to make extension methods that encrypt and decrypt strings in C#


This example uses the .NET cryptography library to make extension methods that make encrypting and decrypting strings easier. 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.

The following ToHex extension method converts a byte array into a string representation.

// Convert a byte array into a string of hexadecimal values. public static string ToHex(this byte[] bytes) { return BitConverter.ToString(bytes, 0).Replace("-", " "); }

This code simply calls BitConverter.ToString to do most of the work. That method returns a byte array's representation as a sequence of two-digit hexadecimal values separated by dashes. The extension method replaces the dashes with spaces and returns the result.

The following ToBytes extension method reverses the process.

// Convert a string containing 2-digit hexadecimal // values separated by spaces or dashes into a byte array. public static byte[] ToBytes(this string hex) { // Separate the bytes. string[] values = hex.Split(' ', '-'); // Make room. int num_bytes = values.Length; byte[] bytes = new byte[num_bytes]; // Parse the byte representations. for (int i = 0; i < num_bytes; i++) bytes[i] = Convert.ToByte(values[i], 16); return bytes; }

This code splits the hexadecimal string at dash and space characters and allocates a byte array long enough to hold the converted values. It then loops through the values, converts them into byte values, and saves the results in the array. It finishes by returning the array.

(Download the example to see a LINQ version. It's much shorter, although undoubtedly less efficient.)

The real cryptographic work is done by the following 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(); aes_provider.Padding = PaddingMode.Zeros; // 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, 0x20, 0x11, 0x27, 0x3A, 0xB4, 0x57, 0xC6, 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 (Advanced Encryption Standard) encryption method.

The code then sets the crypto provider's Padding property to Zeros. AES is a block cipher so the message is broken into blocks of uniform size and then the blocks are encrypted. If the message's size doesn't divide evenly into the blocks, the Padding property tells the provider how to pad the last block.

You can use any of the possible values, but if you don't use Zeros and the password is incorrect during decryption, then the provider throws a CryptographicException with the confusing message "Padding is invalid and cannot be removed." You can use a try catch block to catch the exception and not tell the potential attacker that there has been a problem. (That may even be more secure than setting Padding = Zeros.) This example uses Padding = Zeros so the program can display the incorrectly decrypted message.

Next, the program must make a key and initialization vector (IV) to initialize the service provider. It starts by finding a supported key size. The code 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 (Initialization Vector). 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 data.

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" and to slow the program down. (So an attacker cannot try random passwords as quickly.) In this case, it applies a pseudo-random number generator based on the HMACSHA1 algorithm 1000 times to generate its bytes.

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

With all of that setup, the program uses the following code to encrypt a message.

// Encrypt the text. private void btnEncrypt_Click(object sender, EventArgs e) { byte[] bytes = txtPlaintext.Text.Encrypt(txtPassword.Text); txtCiphertext.Text = bytes.ToHex(); }

This code gets the message entered in the txtPlaintext text box and uses the Encrypt extension method to encrypt it using the password entered in the txtPassword text box. It then calls the resulting byte array's ToHex extension method to convert the bytes into a string and displays the result.

The following code shows how the program decrypts a string.

// Decrypt the text. private void btnDecrypt_Click(object sender, EventArgs e) { byte[] ciphertext = txtCiphertext.Text.ToBytes(); txtDeciphered.Text = ciphertext.Decrypt(txtPassword.Text); }

This code gets the encrypted string from the txtCiphertext text box. It uses the ToBytes extension method to convert the string into a byte array. It then uses the Decrypt extension method to decrypt the bytes. Finally it displays the decrypted message.

Very Important Note: Never store passwords 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 when you decrypt a file, you must use exactly the same password you used to encrypt it. If the password is off by even a single character, the result will be complete gibberish. Try encrypting a message and changing one character in the password to see the result.

Download the example to experiment with it and to see additional details.

© 2009-2023 Rocky Mountain Computer Consulting, Inc. All rights reserved.