You have a string you want to be able to encrypt and decrypt—perhaps a password or software key—which will be stored in some form accessible by users, such as in a file, the registry, or even a field, that may be open to attack from malicious code.
Encrypting the string will prevent users from being able to read and
decipher the information. The following class,
CryptoString
, contains two static methods to
encrypt and decrypt a string and two static properties to retrieve
the generated key and inititialization vector (IV—a random
number used as a starting point to encrypt data) after encryption has
occurred:
using System; using System.Security.Cryptography; public sealed class CryptoString { private CryptoString( ) {} private static byte[] savedKey = null; private static byte[] savedIV = null; public static byte[] Key { get { return savedKey; } set { savedKey = value; } } public static byte[] IV { get { return savedIV; } set { savedIV = value; } } private static void RdGenerateSecretKey(RijndaelManaged rdProvider) { if (savedKey == null) { rdProvider.KeySize = 256; rdProvider.GenerateKey( ); savedKey = rdProvider.Key; } } private static void RdGenerateSecretInitVector(RijndaelManaged rdProvider) { if (savedIV == null) { rdProvider.GenerateIV( ); savedIV = rdProvider.IV; } } public static string Encrypt(string originalStr) { // Encode data string to be stored in memory byte[] originalStrAsBytes = Encoding.ASCII.GetBytes(originalStr); byte[] originalBytes = {}; // Create MemoryStream to contain output MemoryStream memStream = new MemoryStream(originalStrAsBytes.Length); RijndaelManaged rijndael = new RijndaelManaged( ); // Generate and save secret key and init vector RdGenerateSecretKey(rijndael); RdGenerateSecretInitVector(rijndael); if (savedKey == null || savedIV == null) { throw (new NullReferenceException( "savedKey and savedIV must be non-null.")); } // Create encryptor, and stream objects ICryptoTransform rdTransform = rijndael.CreateEncryptor((byte[])savedKey. Clone( ),(byte[])savedIV.Clone( )); CryptoStream cryptoStream = new CryptoStream(memStream, rdTransform, CryptoStreamMode.Write); // Write encrypted data to the MemoryStream cryptoStream.Write(originalStrAsBytes, 0, originalStrAsBytes.Length); cryptoStream.FlushFinalBlock( ); originalBytes = memStream.ToArray( ); // Release all resources memStream.Close( ); cryptoStream.Close( ); rdTransform.Dispose( ); rijndael.Clear( ); // Convert encrypted string string encryptedStr = Convert.ToBase64String(originalBytes); return (encryptedStr); } public static string Decrypt(string encryptedStr) { // Unconvert encrypted string byte[] encryptedStrAsBytes = Convert.FromBase64String(encryptedStr); byte[] initialText = new Byte[encryptedStrAsBytes.Length]; RijndaelManaged rijndael = new RijndaelManaged( ); MemoryStream memStream = new MemoryStream(encryptedStrAsBytes); if (savedKey == null || savedIV == null) { throw (new NullReferenceException( "savedKey and savedIV must be non-null.")); } // Create decryptor, and stream objects ICryptoTransform rdTransform = rijndael.CreateDecryptor((byte[])savedKey. Clone( ),(byte[])savedIV.Clone( )); CryptoStream cryptoStream = new CryptoStream(memStream, rdTransform, CryptoStreamMode.Read); // Read in decrypted string as a byte[] cryptoStream.Read(initialText, 0, initialText.Length); // Release all resources memStream.Close( ); cryptoStream.Close( ); rdTransform.Dispose( ); rijndael.Clear( ); // Convert byte[] to string string decryptedStr = Encoding.ASCII.GetString(initialText); return (decryptedStr); } }
The
CryptoString
class follows a singleton design
pattern. This class contains only static members, except for the
private instance constructor, which prevents anyone from directly
creating an object from this class.
This
class uses the Rijndael algorithm to encrypt and
decrypt a string. This algorithm is found in the
System.Security.Cryptography.RijndaelManaged
class. This algorithm requires a secret key and an initialization
vector; both are byte
arrays. A random secret key
can be generated for you by calling the
GenerateKey
method
on the RijndaelManaged
class. This method accepts
no parameters and returns void
. The generated key
is placed in the Key
property of the
RijndaelManaged
class. The
GenerateIV
method
generates a random initialization vector and places this vector in
the IV
property of the
RijndaelManaged
class.
The byte
array values in the
Key
and IV
properties must be
stored for later use and not modified. This is due to the nature of
private-key encryption classes, such as
RijndaelManaged
. The Key
and
IV
values must be used by both the encryption and
decryption routines to successfully encrypt and decrypt data.
The SavedKey
and SavedIV
private static fields contain the secret key and initialization
vector, respectively. The secret key is used by the encryption and
decryption methods to encrypt and decrypt data. This key must be used
by both the encryption and decryption methods in order to
successfully encrypt and then decrypt the data. This is why there are
public properties for these values, so they can be stored somewhere
secure for later use. This means that any strings encrypted by this
object must be decrypted by this object. The initialization vector is
used to prevent anyone from attempting to decipher the secret key.
There are two methods in the
CryptoString
class,
RdGenerateSecretKey
and
RdGenerateSecretInitVector
, that are used to
generate a secret key and initialization vector, when none exist. The
RdGenerateSecretKey
method generates the secret
key, which is placed in the SavedKey
field.
Likewise, the RdGenerateSecretInitVector
generates
the initialization vector, which is placed in the
SavedIV
field. There is only one key and one IV
generated for this class. This enables the encryption and decryption
routines to have access to the same key and IV information at all
times.
The
Encrypt
and Decrypt
methods of
the CryptoString
class do the actual work of
encrypting and decrypting a string, respectively. The
Encrypt
method accepts a string that you want to
encrypt and returns an encrypted string. The following code calls
this method and passes in a string to be encrypted:
string encryptedString = CryptoString.Encrypt("MyPassword"); Console.WriteLine("encryptedString: " + encryptedString); // get the key and IV used so you can decrypt it later byte [] key = CryptoString.Key; byte [] IV = CryptoString.IV;
Once the string is encrypted, the key and IV are stored for later decryption. This method displays:
encryptedString: Ah4vkmVKpwMYRT97Q8cVgQ==
The following code sets the key and IV used to encrypt the string,
then calls the Decrypt
method to decrypt the
previously encrypted string:
CryptoString.Key = key; CryptoString.IV = IV; string decryptedString = CryptoString.Decrypt(encryptedString); Console.WriteLine("decryptedString: " + decryptedString);
This method displays:
decryptedString: MyPassword
There does not seem to be any problems with using escape sequences
such as
,
,
, or
in the string to be
encrypted. In addition, using a quoted string literal, with or
without escaped characters, works without a problem:
@"MyPassword"
See Recipe 3.32 ; see the “System.Cryptography Namespace,” “MemoryStream Class,” “ICryptoTransform Interface,” and “RijndaelManaged Class” topics in the MSDN documentation.