1, Overview of symmetric encryption in c#
Symmetric encryption algorithm uses the same key for encryption and decryption. The Framework provides four symmetric encryption algorithms. AES,DES,Rijndael,RC2.
DES: the full name is Data Encryption Standard, that is, Data Encryption Standard. It is a block algorithm using key encryption. In 1977, it was determined as the federal data processing standard (FIPS) by the National Bureau of standards of the federal government of the United States and authorized to be used in unclassified government communications. Subsequently, the algorithm was widely spread internationally.
RC2: RC2 is a traditional symmetric block encryption algorithm designed by the famous Cryptologist Ron Rivest. It can be used as a recommended alternative to DES algorithm.
Rijndael: this class is from NET Framework 1.0 already exists.
Aes: this class is in NET Framework 3.5.
Among these algorithms, Rijndael algorithm is the best. Rijndael is fast and safe. It has two implementations:
Rijndael and Aes are almost equivalent, but Aes does not allow the encryption strength to be weakened by changing the block size. The CLR security team recommends using the Aes class.
Rijndael and Aes support 16 byte, 24 byte and 32 byte symmetric key lengths: these lengths are currently considered secure.
For details of AES encryption algorithm, please refer to the following article.
2, Encryption and decryption example
//encryption byte[] key = { 99, 66, 33, 11, 8, 6,59,68,21,25,36,21,24,6,6,6 }; byte[] iv = { 99, 66, 33, 11,5,5,5,5,5,6,6,6,5,5,5,9 }; byte[] data = { 1, 1, 2, 2, 3, 3, 4, 4, 5, 5 }; using (SymmetricAlgorithm symmetric = Aes.Create()) { using (ICryptoTransform tran = symmetric.CreateEncryptor(key, iv)) { using (Stream f = File.Create("aes.txt")) { using (Stream c = new CryptoStream(f, tran, CryptoStreamMode.Write)) { c.Write(data, 0, data.Length); } } } } //decrypt using (SymmetricAlgorithm symmetric = Aes.Create()) { using (ICryptoTransform tran = symmetric.CreateDecryptor(key, iv)) { using (Stream f = File.OpenRead("aes.txt")) { using (Stream c = new CryptoStream(f, tran, CryptoStreamMode.Read)) { for(int b; (b=c.ReadByte())>-1;) { Console.WriteLine(b + " "); } } } } }
If the wrong key is used for decryption, CryptoStream will throw a CryptographicException, and capturing this exception is the only way to test whether the key is correct.
In addition to the key, an initialization vector (IV) is generated in the example. This 16 byte sequence is also part of the password (similar to the key), but it is not confidential. When transmitting encrypted information, IV will transmit in clear text (possibly in the header of the message), but its value is different in each piece of information. In this way, even if the unencrypted versions of some messages are similar, the encrypted information will be difficult to identify.
If the protection of IV is not required, the 16 byte key can be the same as the value of IV. However, sending multiple messages using the same IV will weaken the security of the password and greatly increase the possibility of cracking.
CryptoStream is like a plumber. It focuses on the processing of streams. Therefore, we can replace Aes with another symmetric encryption algorithm and still use CryptoStream.
CryptoStream is bidirectional. Therefore, you can use cryptostreammode Read to read the stream, or cryptostreammode Write to write to the stream. Encryptors and decryptors can form four combinations with reading and writing. These combinations can be confusing! To help understand, read can be understood as "pull" and write as "push". If you still have questions, you can first encrypt with the write operation and decrypt with the read operation. This combination is the most natural and common.
Please use system RandomNumberGenerator under the cryptography namespace to generate a random key or IV. The numbers generated by such random number generators are truly unpredictable or cryptographically strong (which System.Random cannot guarantee). Here is an example:
//Randomly generated key byte[] key_random = new byte[16]; byte[] iv_random = new byte[16]; RandomNumberGenerator rand = RandomNumberGenerator.Create(); //Randomly generate a key rand.GetBytes(key_random); //Randomly generate an iv rand.GetBytes(iv_random);
If the Key and IV are not specified, the encryption algorithm will automatically generate a random number of cryptographic strength as the Key and IV. These values can be viewed through the Key and IV attributes of the Aes object.
3, Memory encryption
We can use MemoryStream to completely put encryption and decryption in memory.
public class AesTools { static byte[] Encrypt(byte[] data, byte[] key, byte[] iv) { using (SymmetricAlgorithm symmetric = Aes.Create()) { symmetric.KeySize = 128; symmetric.BlockSize = 128; symmetric.Mode = CipherMode.CBC; symmetric.Padding = PaddingMode.PKCS7; using (ICryptoTransform tran = symmetric.CreateEncryptor(key, iv)) { return EnCrypt(data, tran); } } } static byte[] Decrypt(byte[] data, byte[] key, byte[] iv) { using (SymmetricAlgorithm symmetric = Aes.Create()) { symmetric.KeySize = 128; symmetric.BlockSize = 128; symmetric.Mode = CipherMode.CBC; symmetric.Padding = PaddingMode.PKCS7; using (ICryptoTransform tran = symmetric.CreateDecryptor(key, iv)) { return DeCrypt(data, tran); } } } static byte[] EnCrypt(byte[] data, ICryptoTransform tran) { return tran.TransformFinalBlock(data, 0, data.Length); } static byte[] DeCrypt(byte[] data, ICryptoTransform tran) { return tran.TransformFinalBlock(data, 0, data.Length); } public static string Encrypt(string data, byte[] key, byte[] iv) { return Convert.ToBase64String(Encrypt(Encoding.UTF8.GetBytes(data), key, iv)); } public static string Decrypt(string data, byte[] key, byte[] iv) { return Encoding.UTF8.GetString(Decrypt(Convert.FromBase64String(data), key, iv)); } }
Test code
//Randomly generated key byte[] key_random = new byte[16]; byte[] iv_random = new byte[16]; RandomNumberGenerator rand = RandomNumberGenerator.Create(); //Randomly generate a key rand.GetBytes(key_random); //Randomly generate an iv rand.GetBytes(iv_random); string encrypted = AesTools.Encrypt("yeah Little cat", key_random, iv_random); Console.WriteLine(encrypted); string decrypted = AesTools.Decrypt(encrypted, key_random, iv_random); Console.WriteLine(decrypted);
You can see the following output
4, Concatenated encrypted stream
CryptoStream is a decorator that can concatenate other streams. The following example writes compressed encrypted text to a file before reading it from the file.
All variables with a single character name are part of the chain. Those algorithm s, encryptor s and decryptor s will assist CryptoStream in encryption.
Regardless of the size of the stream, the above method of encrypting the stream in series will only occupy very little memory.
V. destroy encrypted objects
Destroying CryptoStream ensures that the data cached inside the encrypted stream is flushed to the underlying stream. The internal cache of the encrypted stream is very necessary because the encryption algorithm will process the data in blocks rather than byte by byte.
Unlike other streams, CryptoStream's Flush method does not do anything. If you need to refresh the encrypted stream (but not destroy it), you must use the FlushFinalBlock method. Unlike Flush, FlushFinalBlock can only be called once, and after the call, the stream can no longer write any data.
In our example, we also destroyed Aes algorithm objects and ICryptoTransform objects (encryptor and decryptor). Rijndael transformation does not require actual destruction, but destruction still plays an important role. This operation will clear the symmetric key and associated data from memory to prevent other software running on the machine (especially malware) from detecting these data. We cannot rely on the garbage collector to perform this operation, because garbage collection only marks the memory as unused, and does not fill every byte of memory with zero.
In addition to using to destroy Aes objects, you can also use the Clear method. Its destruction semantics are little known, so it hides the Dispose method in an explicit implementation.
6, Key management recommendations
Hard coding the encryption key is not desirable because you can decompile an assembly into readable code using common tools. A better solution is to make a random key at each installation and use Windows Data Protection to store it safely (or use Windows Data Protection to encrypt the whole message). If you want to encrypt the message flow, public key encryption is still the best choice at present.