.NET Security and Cryptography
One problem with symmetric cryptographic schemes is that distinct keys must be stored on each machine for each communicating pair to enable the sending and receiving of encrypted data. This means that the number of keys that need to be maintained as secrets by all communicating parties grows rapidly as the number of parties increases . If you have N parties, then the maximum number of keys that are potentially needed is the sum of the numbers from 1 to N “ 1. Figure 3-12 shows how these keys start to proliferate for N = 1 and N = 4. Note that the number of required keys grows faster than the number of communicating parties. We will see how to solve this problem efficiently in the next chapter, using asymmetric algorithms to reduce the number of keys required by multiple parties so that the number of private keys remains equal to the number of parties. Figure 3-12. Key proliferation with symmetric cryptography.
There is another problem that must be addressed regarding symmetric cryptography. Despite that the symmetric key proliferation problem can be annoying, symmetric algorithms are powerful, fast, and generally more suitable for bulk data encryption than asymmetric algorithms. Therefore, we must be able to exchange symmetric session keys. The problem is that sharing a secret key requires the secure communication of that key from one party to another in order to establish a secure communications channel. This appears to be a catch-22 [10] situation, since it seems that you need to be able to communicate securely before you can start to communicate securely! Fortunately, there are reasonable solutions to this riddle. [10] If you could send the secret key securely, then you might argue that you wouldn't need the symmetric cryptosystem in the first place, since you could just use that secure channel to send your message instead of the key. Possible solutions to this riddle are to use a temporarily available secure channel, a trusted courier, or an asymmetric key exchange protocol, such as the RSA or Diffie-Hellman key exchange algorithms. The NaiveKeyExchange program examples ( Sender.exe and Receiver.exe ) show a simple-minded approach to exchanging keys by directly writing and reading the key information via an unencrypted disk file. Alternatively, an ordinary socket connection could be used to communicate the keys over a network. The obvious problem with this is that the secret key is not really a secret at all. In fact, it is accessible in the clear, which could be exploited by an interloper if you are not careful. This key exchange scheme works better if special care is taken to ensure that the key was not exposed, such as by encrypting it before being sent. This of course appears to be another catch-22! [11] We will see how to solve this key exchange conundrum in the next chapter, using the RSACryptoServiceProvider and CspParameters classes to encrypt and store the symmetric session key in a very convenient and secure manner. [11] This catch-22 results from our need to encrypt the key that will be used to encrypt the data. This implies that for the encrypted key to be secure, we would need to encrypt the key that encrypted the key that encrypted the data, and so on ad infinitum. Encrypted Hash Codes and Message Integrity
The primary concerns in security are confidentiality, integrity, and authentication. We have seen how symmetric encryption can be used to achieve confidentiality, (i.e., keeping data secret from unauthorized parties). Now, what about integrity and authentication? Integrity means preventing, or at least detecting, unauthorized modification of the data. Authentication means determining the true origin or author of the data. It turns out that both symmetric encryption and asymmetric encryption algorithms can be used to ensure integrity and authentication. We now look at how symmetric encryption can be used for these purposes. Chapter 4 explains how to use asymmetric encryption techniques, such as RSA and DSA, which can be used for applying digital signatures to data to assure integrity and authentication. Assume that Alice and Bob are the only people who know the value of a secret shared key. Also, assume that Alice and Bob trust one another to keep the key secret from everyone else. If either one ever receives any encrypted message that can be successfully decrypted into a valid plaintext message, then they can be quite confident that the message did indeed come from the other person who knows the secret key. This is because it would be exceedingly difficult for any other person to successfully create a valid ciphertext without knowing the key. The only question that remains here is what "valid plaintext message" means in the previous paragraph. The most effective way to prove the validity of a message is to calculate a cryptographic hash value from the message. As long as the message remains unmodified (i.e., valid), its hash function can be recalculated and compared against the original hash value. If the hash value has not changed, the probability that the message has not been altered is exceedingly high. Of course, it is not really sufficient to just calculate this hash value, since an adversary can also calculate hash functions at will, allowing that person to modify the message along with a brand new hash value. This would not effectively support either integrity or authentication. However, if Alice first calculates the hash value and then encrypts that hash value with a secret key, then this becomes a very effective solution. Alice then sends the message (encrypted or nonencrypted, since we are not discussing confidentiality now) along with the encrypted hash to Bob. Bob can decrypt the received hash and compare it against a freshly recalculated hash on the received message data. If the hashes match, then integrity and authentication are verified to a very high probability. The adversary now has a difficult challenge indeed. If he were to modify the message, he could calculate a new hash value, but he could not encrypt a new hash value without knowing the secret key, and the original hash value would no longer match the modified message data. There is now a bit of explanation required on exactly what is meant by the term cryptographic hash . Let's start by describing a slightly weaker term. A hash function is any function that maps an arbitrary-length input data to produce a relatively small, fixed-length data output. The CRC-32 checksum is a simple example of a hash function, which has long been used for data error detection. A cryptographic hash function is basically the same, but it has some additional properties. For example, it must be very difficult to find two distinct inputs that produce the same hash output (which is not true of the CRC-32 checksum). Also, it should be a one-way function, meaning that it is easy to compute the hash value but virtually impossible to reverse-engineer the original data from the hash value. A good cryptographic hash function should also produce many unpredictable bit changes in the hash value even when very small (i.e., single bit) changes are made to the input data. A cryptographic hash is used as manageable- sized data representation that is highly characteristic of the data and very difficult to forge , which makes it quite analogous to a human fingerprint . The following cryptographic hash classes are provided by the .NET framework, all of which are derived from the HashAlgorithm abstract class. All of the following classes on the left are abstract classes that are derived into concrete classes, listed on the right. The MD in MD5 stands for message digest, which is an alternative name for a cryptographic hash. The SHA in SHA-1, SHA-256, and so on stands for Secure Hash Algorithm, which was originally designed by NIST and the NSA for use in the Digital Signature Algorithm (DSA).
The EncryptedHash example program demonstrates how encrypting the cryptographic hash of a message can be used to verify the data's integrity. In this example the plaintext is first hashed using the MD5 hash algorithm, and then that hash value is encrypted using the DES symmetric algorithm. In a realistic scenario, the message and its encrypted hash bytes would be transmitted to a separate receiver program that would decrypt and validate the hash value. However, to keep this example simple and compact, all the work that would normally be done by the sending and receiving programs is done in one simple program. ... //Create MD5 hash bytes from plaintext bytes MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider (); byte[] hashBytes = md5. ComputeHash (plaintextBytes); ... //encrypt hash bytes using DES SymmetricAlgorithm sa = DES.Create (); MemoryStream msEncrypt = new MemoryStream(); CryptoStream csEncrypt = new CryptoStream( msEncrypt, sa.CreateEncryptor(), CryptoStreamMode.Write); csEncrypt. Write (hashBytes, 0, hashBytes.Length); csEncrypt.Close(); byte[] encryptedHashBytes = msEncrypt.ToArray(); msEncrypt.Close(); ... //decrypt hash bytes using DES MemoryStream msDecrypt = new MemoryStream(encryptedHashBytes); CryptoStream csDecrypt = new CryptoStream( msDecrypt, sa.CreateDecryptor(), CryptoStreamMode.Read); byte[] decryptedHashBytes = new Byte[encryptedHashBytes.Length]; csDecrypt. Read (decryptedHashBytes, 0, encryptedHashBytes.Length); csDecrypt.Close(); msDecrypt.Close(); ... //compare original and decrypted hash bytes bool match = true; for (int i=0; i<hashBytes.Length; i++) { if(hashBytes[i] != decryptedHashBytes[i]) { match = false; break; } } if(match) Console.WriteLine( "The hash values match!"); else Console.WriteLine( "The hash values do not match!"); Keyed Hash Functions and Message Integrity
A keyed hash algorithm is a cryptographic hash function that takes a key as an additional parameter when it is used to hash the data. This means that you do not have to explicitly take the second step of encrypting the hash to ensure data integrity or to prove authenticity, since this effectively happens automatically. The KeyedHashAlgorithm abstract class represents all implementations of keyed hash algorithms. Currently, there are only two derived implementation classes: HMACSHA1 and MACTripleDES . These concrete classes are used to determine if data has been tampered with, based on the assumption that only sender and receiver know the secret key. In this example, the sender computes the HMAC [12] hash for the data, using the secret key, and sends it along with the original data. The receiver recalculates the HMAC hash on the received data, using the same key, and verifies that the data is legitimate . Since virtually any change that might be made to the data produces a mismatched hash value, the message can thus be validated . HMACSHA1 accepts any key size and produces a 20-byte hash value. [12] HMAC is a mechanism for message authentication using cryptographic hash functions. See RFC 2104. The HMACSHA1Example example shows how to calculate keyed hash bytes on a text string entered by the user . Again, no attempt is made here to implement sender and receiver programs. Instead, a simple program is used to show the basic ideas. An actual sender and receiver would simply calculate the keyed hash independently, and the receiver would compare the result with the keyed hash calculated by the sender. If they match, the message is authenticated; otherwise , it is deemed to be corrupt. The only provision that would need to be made is that the sender and receiver must secretly agree beforehand on the key to be used. Then, the sender sends the message, along with the associated keyed hash value to the receiver. The receiver recalculates the keyed hash value with the secret key and compares the result. //get plaintext string from user Console.WriteLine("Enter a plaintext string:"); String plaintextString = Console.ReadLine(); //Convert plaintext string to byte array Byte[] plaintextBytes = (new UnicodeEncoding()).GetBytes( plaintextString); //Create keyed hash bytes from plaintext bytes byte[] key = new byte[16]; HMACSHA1 hmac = new HMACSHA1(key); CryptoStream cs = new CryptoStream ( Stream.Null, hmac , CryptoStreamMode.Write); cs.Write( plaintextBytes, 0, plaintextBytes.Length); cs.Close(); byte[] keyedHashBytes = hmac.Hash; //display keyed hash bytes Console.WriteLine( "Keyed hash bytes of plaintext string:\n" + BitConverter.ToString(keyedHashBytes)); |