Encrypting Data in .NET
Cryptology and cryptography are exact studies in mathematics. Very smart mathematicians are working every day on new and better ways to conceal data. Fortunately, programmers do not need to understand—although they are interesting—the algorithms used to encrypt data. Although an interesting and specialized area of computer science, programmers can use the most powerful encryption algorithms known today by using the System.Security.Cryptography—known as the CryptoAPI—namespace in .NET.
Cryptography, and the various algorithms it encompasses, makes for interesting reading but exceeds my ability to discuss extemporaneously without a serious refresher course in the math and is unlikely to fit in the space we have. Instead, this article introduces a small but practical facet of the CryptoAPI. If you want to conceal chunks of data, such as connection strings or data written to a database or XML file, you could use the simplified class resting on the CryptoAPI demonstrated in this article.
There are several popular encryption algorithms. Referred to by name in the CryptoAPI, some of them are SHA1, TripleDES, RSA, and MD5. (Further study of the individual algorithms can be done by looking up the algorithm by name on the Internet.) In this article, I will demonstrate how to use the TripleDES—three iterations of the Data Encryption Standard—algorithm provided by the TripleDESCryptoServiceProvider defined in the CryptoAPI.
Encrypting and Decrypting Data
The symmetric encryption algorithms in the CryptoAPI object model are implemented in such a way that we need a stream to store the encrypted or decrypted data. We need an implementation of the CryptoStream class to perform the transform. The TripleDES algorithm requires a secret key and a second vector key to perform the encryption and decryption. Finally, we need to write the bytes to be encrypted or decrypted to the CryptoStream in order to perform the transform.
For practical purposes, we need to store the encrypted data and the keys used to perform the transform. A potential liability exists because ultimately we need to store some piece of data, the key, which can lead to uncovering our secret information. In our slimmed-down encryption mechanism, we will use a simple scheme to create the key; we will derive the key from the Windows Identity name of the user running the program. As a result, the data is decrypted when logged on using the credentials and application used to encrypt the data. This implies that a person attempting to discover your secret would need to know your password too. Granted, it is possible to contrive a similar scheme and write some code that uses the original user's name, but the would-be crook would need to know the encrypting user's Windows Identity, have access to the encrypted data, and figure out the exact algorithm used to create the key and vector key. Collectively, this approach to encrypting data is pretty secure and is especially convenient.
Listing 1 contains a class I named Encryptor. Because this class' keys are contrived—and variations of this approach could be easily devised—this class could be used to encrypt data in an unattended process and would work reasonably well in a secure environment where access to the physical box and data is limited.
Listing 1: The Encryptor class.
1: Public Class Encryptor 2: Private Shared des As TripleDESCryptoServiceProvider = _ 3: New TripleDESCryptoServiceProvider 4: 5: Private Shared ReadOnly Property Key() As Byte() 6: Get 7: Return Encoding.Default.GetBytes( _ 8: WindowsIdentity.GetCurrent.Name.PadRight(24, Chr(0))) 9: End Get 10: End Property 11: 12: Private Shared ReadOnly Property Vector() As Byte() 13: Get 14: Return Encoding.Default.GetBytes( _ 15: WindowsIdentity.GetCurrent().Name.PadRight(8, Chr(0))) 16: End Get 17: End Property 18: 19: Public Shared Function Encrypt(ByVal Text As String) _ As String 20: Return Transform(Text, des.CreateEncryptor(Key, Vector)) 21: End Function 22: 23: Public Shared Function Decrypt( _ 24: ByVal encryptedText As String) As String 25: Return Transform(encryptedText, _ des.CreateDecryptor(Key, Vector)) 26: End Function 27: 28: Private Shared Function Transform(ByVal Text As String, _ 29: ByVal CryptoTransform As ICryptoTransform) As String 30: 31: Dim stream As MemoryStream = New MemoryStream 32: Dim cryptoStream As CryptoStream = _ 33: New CryptoStream(stream, CryptoTransform, _ CryptoStreamMode.Write) 34: 35: Dim Input() As Byte = Encoding.Default.GetBytes(Text) 36: 37: cryptoStream.Write(Input, 0, Input.Length) 38: cryptoStream.FlushFinalBlock() 39: 40: Return Encoding.Default.GetString(stream.ToArray()) 41: End Function 42: End Class
The two public methods are the factored Encrypt and Decrypt. Pass in plain text to Encrypt to obfuscate the text, and pass in encrypted text to decipher the text. My simple algorithm is based on a 24-byte array contrived from a 0-padded Windows Identity name—see lines 5 through 10—and an 8-byte array contrived in the same manner. With a simple modification to the algorithm—for example, by using different padding characters or the machine's name—one could create a unique algorithm.
Most of the work is accomplished in the Transform method. Transform accepts a string and an object that implements ICryptoTransform. To encrypt data and satisfy the ICryptoTransform argument, we can request an encryptor using TripleDESCryptoServiceProvider.CreateEncryptor; and to decrypt data, we can request a decryptor using TripleDESCryptoServiceProvider.CreateDecryptor. Aside from the ICryptoTransform argument, the basic code to encrypt and decrypt in our basic example is identical and occurs in the Transform method.
Transform creates a MemoryStream (see line 31) that acts as a repository for storing the transformed data. To write the transformed data to a file, we could substitute a FileStream here; and to write the transformed data to a network socket, we could substitute a NetworkStream here. The CryptoStream object (see lines 32 and 33) is initialized with our target stream, the ICryptoTransform that performs the transformation, and an enumeration that describes the action the transform should perform. Line 35 uses the default encoding—defined in System.Text—to convert the input string to an array of bytes because the CryptoStream.Write method is defined to work with bytes. Next, we write the array of bytes to the CryptoStream that implicitly performs the transform and flushes the stream to ensure it has finished transforming all submitted text. Finally, we request the array of transformed bytes from the repository stream and use the default encoding to convert the transformed bytes back to a string.
One might reasonably ask why all of these intermediate objects are needed to encrypt and decrypt data: The answer is flexibility. For example, by separating the cryptographic algorithms from the storage repository, we can reuse existing storage repositories—streams here—instead of duplicating that code in the cryptography classes. Additionally, polymorphic behavior permits developers to redirect output to a file or across an IP-based network simply by changing the type of the repository stream. Flexibility makes frameworks more powerful, but flexibility does add complexity. The key is for framework designers to program for a general audience, and individual programmers often have to wrap this generalized flexibility in such a way as to facilitate intra-enterprise consumption.
It is important for the individual to decide the suitability of any specific approach to securing data. The example demonstrated in this article, like any obfuscation scheme, is not guaranteed to secure your data under all circumstances, but is better than nothing.
In this article, you were introduced to the CryptoAPI—the System.Security.Cryptography namespace—in .NET. TripleDES is one encryption algorithm and the TripleDES class in .NET alone eliminates the need for you to master cryptography and any specific algorithm. All you and I need to do is figure out how to create and store a pair of keys and to write a few lines of code. This is a tremendous simplification relative to mastering the mathematics of encryption, and simplification is ultimately the benefit of adopting a framework.
About the Author
Paul Kimmel is a software architect, writer, and columnist for codeguru.com. Look for his recent book Visual Basic .NET Power Coding from Addison-Wesley on Amazon.com. Paul Kimmel is available to help design and build your .NET solutions and can be contacted at firstname.lastname@example.org.
# # #