Now you know the story at a high level. The Encrypt
extension methods encrypt a string. The Decrypt
extension methods decrypt a byte array or string to recover the original message. But I haven't explained how the actual encryption works.
All these extension methods depend directly or indirectly on the CryptBytes
helper function shown in Listing 1
starts by making a triple DES crypto provider. This is an object that uses the DES encryption algorithm three times to encrypt or decrypt data. You can pick from among other crypto providers if you like.
Next the function tries to find a key size that is supported by the operating system. Different versions of the operating systems in different countries support different key sizes so the code must find one that works on this system. (If you're going to send an encrypted message to someone else, make sure they use the same key size, particularly if you're in different countries.) The code starts with a big value (1024
) and works downwards until it finds one that works.
The crypto provider encrypts and decrypts data in blocks. The code records the provider's block size for later use.
Next the routine builds a "salt" array. (Don't blame me; "salt" is a cryptographic term that's been around for a while so we're stuck with it.) The salt array is an array of "random" bytes that you pick to make it harder for cyber-desperados to use a dictionary attack that guesses every possible password for your message. The salt guarantees that they'll also have to guess the salt.
|Author's Note: You must use the same salt when you encrypt and decrypt or the decryption won't work. For increased security, use different salt values than the ones shown here.
The code then calls helper routine MakeKeyAndIV
. I'll explain that one in a minute but for now just accept that this routine creates a key and initialization vector (IV) to initialize the crypto provider.
Next the code makes a cryptographic transformation object to either encrypt or decrypt the byte array. It makes a memory stream to hold the result and builds a CryptoStream
object attached to the stream and the transformation object.
Finally the code is ready to perform the encryption or decryption. The routine writes the byte array into the CryptoStream
object. That object transforms the data and writes the result into the output memory stream.
The code finishes by converting the memory stream into a new array of bytes and returning it.
(Are you starting to see why I thought it would be nice to wrap this mess in a nice, simple extension method?)
The following code shows the final piece to the puzzle: the helper subroutine MakeKeyAndIV
' Use the password to generate key bytes.
Private Sub MakeKeyAndIV(ByVal password As String, _
ByVal salt() As Byte, ByVal key_size_bits As Integer, _
ByVal block_size_bits As Integer, ByRef key() As Byte, _
ByRef iv() As Byte)
Dim derive_bytes As New Rfc2898DeriveBytes( _
password, salt, 1234)
key = derive_bytes.GetBytes(key_size_bits \ 8)
iv = derive_bytes.GetBytes(block_size_bits \ 8)
This subroutine makes an Rfc2898DeriveBytes
object. This object uses the HMACSHA1 algorithm (don't worry about that!) to generate a series of pseudo-random bytes. There are other classes that you probably won't recognize either that you can use if you have some sort of grudge against the HMACSHA1 algorithm. The program calls the object's GetBytes
function to grab some random bytes that it can use for the key and initialization vector.
Testing Encryption Extension Methods
|Figure 1. Cryptic Code: Program StringExtensionsDemo encrypts and decrypts strings.|
helper routines are swathed in cryptographic mysticism but the main extension methods Encrypt
, and DecryptFromString
are relatively simple and easy to use. The StringExtensionsDemo
project (see Figure 1
) in the downloadable code
, demonstrates their use in addition to the simpler Left
, and RemoveRight
When you modify the string in the topmost text box, the program uses the Left
, and RemoveRight
methods to display eight characters from the string.
When you enter a message and a password and then click the Encrypt button, the following code executes:
Private Sub btnEncrypt_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnEncrypt.Click
Dim txt As String = txtText.Text
Dim password As String = txtPassword.Text
' Encrypt the string into bytes.
m_EncryptedBytes = txt.Encrypt(password)
' Display a textual representation.
Dim ascii_encoder As New System.Text.ASCIIEncoding()
lblCiphertext1.Text = ascii_encoder.GetChars(m_EncryptedBytes)
' Encrypt and display as a hex byte string.
lblCiphertext2.Text = txt.EncryptToString(password)
btnDecrypt.Enabled = True
The code copies the message and password into string variables. It calls the message's Encrypt
extension method to get an encrypted byte array and saves that in the form-level variable m_EncryptedBytes
. The code then uses an ASCIIEncoding object to convert the array into a string, and displays the result, which looks like gibberish. Finally the code calls the message's EncryptToString
method and displays the encrypted text as a hexadecimal string.
After you encrypt a string, if you click the Decrypt button, the following code executes:
' Decrypt the previously encrypted bytes using the current
Private Sub btnDecrypt_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnDecrypt.Click
Dim password As String = txtPassword.Text
' Decrypt the previously saved byte array.
lblPlaintext1.Text = m_EncryptedBytes.Decrypt(password)
' Decrypt the hex string representation.
Dim hex_str As String = lblCiphertext2.Text
lblPlaintext2.Text = hex_str.DecryptFromString(password)
This code calls the saved byte array's Decrypt
extension method to recover the original message and displays the result. Then, to prove it can, the code retrieves the hexadecimal representation of the encrypted message, calls its DecryptFromString
method, and displays the result.
|Figure 2. Picky Passwords: After changing the leading "I" in the Password field to an "H" and pressing the Decrypt button, the Plaintext result shows that when the password used to decrypt a message does not match the password used to encrypt it, the result is complete gibberish.|
The cryptographic routines are quite strong and produce a pretty secure result as long as your key size is big enough. An important consequence of this is that you must get the password exactly correct
to decode a message. If the password is off by a single character, the CryptoStream
decoder throws an error and the result is complete garbage.
shows the example program trying to decode a message with a password that is off by a single character. In fact, it's off by a single bit (the "H" at the beginning of the password in Figure 2
differs by only one bit from the "I" used to encrypt the message). The result is complete gibberish.
This is important because it means an attacker cannot easily learn about your password. It would be bad if a partially correct password got the attacker a partly correct message. The cryptographic routines ensure that attackers get nothing useful unless they guess the password exactly.
Extension methods can be a powerful tool. As long as you use them judiciously, extension methods can add new features to existing classes, even ones that you didn't write. This article showed how to add some simple text processing methods and some much more complex cryptographic methods to the String
So give extension methods a try and see what interesting ideas you can come up with. Then email your results to me
. I'll post the methods that seem like they might be most useful to others.