The encryption classes don't have methods with names like Encrypt() or Decrypt() . Instead they work by reading from and writing to streams using companion objects called encryptors and decryptors, and special streams known as crypto streams. To encrypt information that is going into a file, you create an ordinary file stream, and then layer a crypto stream on that file stream, and write to the crypto stream. The data is encrypted as it is written. To encrypt one string into another string, you create a memory stream associated with the output stream, and then layer on a crypto stream and write to the crypto stream, which encrypts as it writes to the memory stream, which you can then get into the string.
Encrypting into a File
Here's a simple console application that prompts for a string, and then encrypts it and writes it into a file:
Console::WriteLine(S"Enter a sentence"); String* plaintext = Console::ReadLine(); unsigned char stringbytes __gc = Text::ASCIIEncoding::ASCII->GetBytes(plaintext); FileStream* fs = new FileStream("secret.txt",FileMode::Create); Rijndael* rijndael = Rijndael::Create(); ICryptoTransform* transform = rijndael->CreateEncryptor(); CryptoStream* cs = new CryptoStream(fs, transform, CryptoStreamMode::Write); cs->Write(stringbytes,0,stringbytes->Length); cs->Close(); fs->Close();
The samples in this chapter are console applications. You're most likely to use these techniques in a class library, probably a business layer component.
This code gets a string from the user and then converts it into an array of bytes. Pay special attention to the way the garbage-collected array of unsigned characters is declared:
unsigned char stringbytes __gc
Experienced C++ programmers are often frustrated trying to remember where to place the __gc modifier when declaring a garbage-collected array.
Having created the array, this code opens a file in the project directory called secret.txt. Of course, you don't need to hard-code the filename, but it keeps this sample simpler to use a hard-coded name .
The next step is to create an implementation instance by calling the static Create() method of the Rijndael class. This generates a random key and initialization vector to be used for the encryption.
A call to CreateEncryptor() creates an encryptor that can be passed to the constructor of the crypto stream. The layering means that bytes will go from the byte array to the crypto stream to the file stream, and finally to the file on the hard drive. All the code needs to do is to write to the crypto stream that's wrapped around the file stream. The final step is to close both streams.
You run this console application and enter a short sentence, such as this:
Shh! Don't tell anyone!
Browse to the secret.txt file and open it in Notepad. You'll see content similar to this (the exact characters depend on the key and initialization vector, and yours will differ ):
V-ojpy n (8I 1 Zk +Dy
This is the encrypted text. It's just that simple to encrypt information before you save it.
Arrangements for Keys
Because Rijndael is a symmetric algorithm, anyone who wants to decrypt the contents of secret.txt will need the key and initialization vector that were used to encrypt this text. Symmetric algorithms always rely on a shared secret. You can, for example, write the key into a text file called key.txt and the initialization vector into another file called iv.txtbut that would hardly be secure, especially if you kept them in the same folder as secret.txt. However, if you emailed key.txt and iv.txt to someone, and then later emailed secret.txt, the person would be able to decrypt secret.txt. Unfortunately, so would anyone else who could access your correspondent's email.
You have a variety of options for making the key available. You can choose more obscure filenames, you can save the files onto a disk and courier it directly to the person who is going to read the file, you can even print the key and the initialization vector, fax the printout to your colleague, and get your colleague to retype the files by handbecause that way nothing can be intercepted through email or over the network. Because the initialization vector is only 16 bytes long, and the key only 32 bytes long, you can even read them over the phone to your colleague for retyping. There are many options to choose from, and they all rely on a shared secret.
In the sample for this chapter, you'll see another version of the shared secret technique: The key and the initialization vector are actually in secret.txtunencrypted! Because they are arrays of numbers , they look like the same gibberish as the rest of the encrypted data. Add these two lines before the line that creates the crypto stream:
These write the initialization vector and the key out directly, using the file stream, not the crypto stream. That means later they can be read back before a decrypting crypto stream has been created. This might feel a little dangerous, like leaving the key to your house under the doormat, but it doesn't have to be. Obviously you don't email a file like this to someone and explain in the message how it was encrypted, with what algorithm, and where the key and initialization vector are stored. You can deliver that information in person: Or if you are writing the application that stores and also the application that reads, don't tell anyone, just use the information. And of course, you can make it safer by mixing it up a bit. Just because this sample uses the initialization vector followed by the key doesn't mean you need to. Flip them around. Or put some meaningless characters between them. Write one of them twice, just to be confusing. As long as only you know the secretwhat order they are in, what they're padded with, and so ononly you will be able to decrypt the file.
After making this change and running the application again, try entering the same sentence you encrypted before. The secret.txt file should still contain a lot of strange characters, but it will be a little longer now.