| [ Team LiB ] |
|
17.2 Programming Cryptographic KeysIn the following sections, we demonstrate how to use the .NET support for creating and managing keys. Some of these techniques are functionality equivalent, and when deciding between them for your projects, you must take into consideration the tension between the abilities of the user and the demands for data security; see Section 17.1.1 for details. 17.2.1 Creating KeysIn the following sections, we discuss three techniques for creating keys. Only one of these techniques presents the user with data that is easy to memorize. You must be pragmatic when deciding how to create new keys, and select a process that satisfies the security demands of your project and the practical demands of the users. 17.2.1.1 Using the algorithm classesThe simplest way to create keys is to use the functionality built into all of the .NET algorithm classes for both symmetric and asymmetric algorithms. The .NET classes creates new keys as they are needed; if you attempt to perform any cryptographic operation and you have not explicitly specified the keys to use, then the .NET classes will create new keys automatically. The following statements demonstrate how to use this functionality to print out the key value for a symmetrical algorithm (for full details see Chapter 14): # C#
// create an instance of the symmetric algorithm
SymmetricAlgorithm x_alg = SymmetricAlgorithm.Create("Rijndael");
// set the length of key that we want to create
x_alg.KeySize = 128;
// get the key value, which will cause the implementation
// class to create a new secret key
byte[] x_secret_key = x_alg.Key;
// print out the key
foreach (byte b in x_secret_key) {
Console.Write("{0:X2} ", b);
}
# Visual Basic .NET
' create an instance of the symmetric algorithm
Dim x_alg As SymmetricAlgorithm = SymmetricAlgorithm.Create("Rijndael")
' set the length of key that we want to create
x_alg.KeySize = 128
' get the key value, which will cause the implementation
' class to create a new secret key
Dim x_secret_key As Byte( ) = x_alg.Key
' print out the key
Dim b As Byte
For Each b In x_secret_key
Console.Write("{0:X2} ", b)
Next b
The following statements demonstrate how to create a new key and print out the value for an asymmetric algorithm; unlike most asymmetric functions, obtaining details of the key can be performed through the abstract AsymmetricAlgorithm class (for full details see Chapter 15): # C#
// create an instance of the asymmetric algorithm
AsymmetricAlgorithm x_alg = AsymmetricAlgorithm.Create("RSA");
// set the length of key that we want to create
x_alg.KeySize = 1024;
// get the key value, which will cause the implementation
// class to create a new secret key
string x_key_pair = x_alg.ToXmlString(true);
// print out the key
Console.WriteLine(x_key_pair);
# Visual Basic .NET
' create an instance of the asymmetric algorithm
Dim x_alg As AsymmetricAlgorithm = AsymmetricAlgorithm.Create("RSA")
' set the length of key that we want to create
x_alg.KeySize = 1024
' get the key value, which will cause the implementation
' class to create a new secret key
Dim x_key_pair As String = x_alg.ToXmlString(True)
' print out the key
Console.WriteLine(x_key_pair)
The principal benefit of creating keys in this manner is that you do not have to have any prior knowledge about how the key should be created; the algorithm class is responsible for creating keys in accordance with the relevant specification. The principle drawback is that the .NET classes generate keys that users will find difficult to remember.
17.2.1.2 Using a random number generatorThe second approach to creating keys is to use a random number generator (RNG). The .NET Framework provides support classes for generating random data; Figure 17-3 illustrates the class hierarchy. Figure 17-3. The.NET Framework class hierarchy for random number generators![]()
The class hierarchy for random number generators follows the abstract class/implementation class model that is used for hashing algorithms and symmetrical algorithms. The .NET Framework includes one implementation class, which is a wrapper around the RNG functions of the native Windows Cryptography API. Table 17-1 details the public members of the abstract RandomNumberGenerator class.
You must know how many bytes of random data to create to prepare a key for a given algorithm correctly. The following statements demonstrate how to use the random number generator to create a key for a symmetric algorithm: # C#
// create an instance of the symmetric algorithm
SymmetricAlgorithm x_alg = SymmetricAlgorithm.Create("Rijndael");
// set the length of key that we want to create
x_alg.KeySize = 128;
// we need to create an array of 16 bytes (16 bytes is 128 bits)
byte[] x_key_data = new byte[16];
// create the RNG
RandomNumberGenerator x_rng = RandomNumberGenerator.Create( );
// use the RNG to populate the byte array with random data
x_rng.GetBytes(x_key_data);
// set the key value for the symmetrical algorithm
x_alg.Key = x_key_data;
# Visual Basic .NET
' create an instance of the symmetric algorithm
Dim x_alg As SymmetricAlgorithm = SymmetricAlgorithm.Create("Rijndael")
' set the length of key that we want to create
x_alg.KeySize = 128
' we need to create an array of 16 bytes (16 bytes is 128 bits)
Dim x_key_data(15) As Byte
' create the RNG
Dim x_rng As RandomNumberGenerator = RandomNumberGenerator.Create( )
' use the RNG to populate the byte array with random data
x_rng.GetBytes(x_key_data)
' set the key value for the symmetrical algorithm
x_alg.Key = x_key_data
17.2.1.3 Using a key-derivation protocolThe previous two approaches result in values that a user will find difficult to remember. Users tend to prefer key values that have some meaning, but keys made up of proper words do not make ideal cryptographic keys; they are more susceptible to brute force attacks, because not all key values are equally likely when we are restricted to alphanumeric values listed in a dictionary. A key-derivation protocol is a compromise between the need to create keys that are difficult to guess and the need for users to remember the key values. This kind of protocol processes a password selected by the user to create a cryptographic key. The user remembers the password, and types it in (rather than a sequence of numeric values). The derivation protocol transforms the password into a cryptographic key. The .NET Framework supports one key-derivation protocol, which is based on the PKCS #5/PBKDF1 standard. We summarize the protocol, as follows:
Key-derivation protocols are deterministic, meaning that they will always create the same cryptographic key when supplied with specific password and salt values. Keys derived from passwords are suitable for symmetric algorithms, but asymmetric algorithms require you to follow the appropriate key-generation protocol to create new key pairs. Keys that are derived in this way are not as secure as those created from random data, and the password should be chosen so that it is difficult to guess. Figure 17-4 illustrates the .NET Framework class hierarchy for derivation protocols. The PasswordDerivedBytes class implements the protocol we described; the members of this class are listed in Table 17-2. Figure 17-4. The .NET Framework class hierarchy for byte derivation schemes![]()
The following statements demonstrate how to use the PasswordDeriveBytes class to derive a key using "Programming .NET Security" as the password; the password and the salt value are specified in the class constructor: # C#
// create the random salt value
byte[] x_salt = new byte[8];
RandomNumberGenerator x_rand = RandomNumberGenerator.Create( );
x_rand.GetBytes(x_salt);
// create the derivation protocol class
PasswordDeriveBytes x_pwd
= new PasswordDeriveBytes("Programming .NET Security", x_salt);
// specify the number of iterations
x_pwd.IterationCount = 100;
// specify the hashing algorithm
x_pwd.HashName = "SHA1";
// create the key
byte[] x_key = x_pwd.GetBytes(16);
// write out the salt value
Console.Write("SALT: ");
foreach (byte b in x_salt) {
Console.Write("{0:X2} ", b);
}
Console.WriteLine( );
// write out the key value
Console.Write("KEY: " );
foreach (byte b in x_key) {
Console.Write("{0:X2} ", b);
}
Console.WriteLine( );
# Visual Basic .NET
' create the random salt value
Dim x_salt(7) As Byte
Dim x_rand As RandomNumberGenerator = RandomNumberGenerator.Create( )
x_rand.GetBytes(x_salt)
' create the derivation protocol class
Dim x_pwd As PasswordDeriveBytes _
= New PasswordDeriveBytes("Programming .NET Security", x_salt)
' specify the number of iterations
x_pwd.IterationCount = 100
' specify the hashing algorithm
x_pwd.HashName = "SHA1"
' create the key
Dim x_key( ) As Byte = x_pwd.GetBytes(16)
' write out the salt value
Dim b As Byte
Console.Write("SALT: ")
For Each b In x_salt
Console.Write("{0:X2} ", b)
Next
Console.WriteLine( )
' write out the key value
Console.Write("KEY: ")
For Each b In x_key
Console.Write("{0:X2} ", b)
Next
Console.WriteLine( )
17.2.2 Using Key PersistenceThe implementation classes for the DSA and RSA algorithms are wrappers around native code contained in the Windows Crypto API. These classes expose a feature of this API that allows asymmetric key pairs to be stored persistently by the operating system; the user does not have to remember the key parameters, which are protected by the Windows account password. Relying on the security of the Windows operating system to protect cryptographic key pairs is not suitable for all projects; projects that require high levels of security are likely to be affected by the relative ease with which a Windows password can be attacked. The full details of the Windows Crypto API are beyond the scope of this book, but in this section, we briefly demonstrate how to use the .NET classes to store key pairs persistently. The System.Security.Cryptography.CspParameters class allows you to specify the details of how a key pair should be stored—there are various options available, but for your purposes, you require only a name to associate with the keys. The name relates to the key store, and you can use any name that suits your needs; the following statements create an instance of the CspParameters class and set the key store name to be MyKeys: # C# // create the parameters instance CspParameters x_params = new CspParameters( ); // specify the container name x_params.KeyContainerName = "MyKeys"; # Visual Basic .NET ' create the parameters instance Dim x_params As CspParameters = New CspParameters( ) ' specify the container name x_params.KeyContainerName = "MyKeys" Once you have created your CspParameters instance, you can use it as the argument to the constructor of the algorithm implementation class. The final step is to set the PersistKeyInCsp property to true, specifying that you want to store the algorithm's key pair persistently. The following statements demonstrate how to create an instance of the RSACryptoServiceProvider class using the CspParameters instance as an argument and set the PersistKeyInCsp property: # C#
// create an instance of the crypto provider class with the csp parameters
RSACryptoServiceProvider x_rsa = new RSACryptoServiceProvider(x_params);
// enable key persistence
x_rsa.PersistKeyInCsp = true;
# Visual Basic .NET
' create an instance of the crypto provider class with the csp parameters
Dim x_rsa As RSACryptoServiceProvider = New RSACryptoServiceProvider(x_params)
' enable key persistence
x_rsa.PersistKeyInCsp = true
The asymmetric implementation classes will create a key pair automatically before performing any cryptographic operations or exporting the key parameters. When the key pair is created, it will be stored persistently and the user will not have to try and remember (or write down) the key details. This technique can be used only with classes that build on functionality of the Windows Crypto API; implementations from third-party companies may not provide this support. 17.2.3 Key Exchange FormattingThe .NET Framework includes classes specifically for exchanging session keys using asymmetric encryption. The process for encrypting a key value is as follows:
The formatter class is responsible for preparing the session key data prior to encryption with the asymmetric algorithm. Figure 17-5 illustrates the .NET class hierarchy for key exchange formatter classes. The .NET class library includes formatting classes for the RSA algorithm; the DSA algorithm is not suitable for session key exchange since it does not support data encryption. Figure 17-5. The .NET Framework class hierarchy for key exchange formatting![]() Two formatting schemes are supported—the OAEP scheme is preferred. The second scheme, PKCS #1 v1.5, is included for compatibility with legacy systems. The formatting scheme is used to protect the session key value from specific attacks; we implement the OAEP scheme in the Section 17.3 of this chapter, but it is not essential that you understand the details of the formatting schemes available, only that you know how to use them. Table 17-3 summarizes the members of the abstract AsymmetricExchangeFormatter class.
Example 17-1 statements demonstrate how to create and format a 128-bit Rijndael session key using the RSA algorithm and the OAEP formatting scheme: Example 17-1. bit Rijndael session key# C#
// create an instance of the Rijndael algorithm and
// specify 128-bits as the key length
Rijndael x_rijndael = Rijndael.Create( );
x_rijndael.KeySize = 128;
// rely on the fact that the algorithm implementation
// class will create a new key to obtain the session value
byte[] x_session_key = x_rijndael.Key;
// create an instance of the RSA algorithm class
RSA x_rsa = new RSACryptoServiceProvider( );
//
// ... specify public key to encrypt with
//
// create a new instance of the RSA OAEP formatter
RSAOAEPKeyExchangeFormatter x_formatter
= new RSAOAEPKeyExchangeFormatter( );
// specify the RSA instance we created as the one
// to use when encrypting the session key data
x_formatter.SetKey(x_rsa);
// encrypt the session key with the formatter
byte[] x_exchange_data = x_formatter.CreateKeyExchange(x_session_key);
// write out the encrypted data
foreach (byte b in x_exchange_data) {
Console.Write("{0:X2} ", b);
}
# Visual Basic .NET
' create an instance of the Rijndael algorithm and
' specify 128-bits as the key length
Dim x_rijndael As Rijndael = Rijndael.Create( )
x_rijndael.KeySize = 128
' rely on the fact that the algorithm implementation
' class will create a new key to obtain the session value
Dim x_session_key( ) As Byte = x_rijndael.Key
' create an instance of the RSA algorithm class
Dim x_rsa As RSA = New RSACryptoServiceProvider( )
'
' ... specify public key to encrypt with
'
' create a new instance of the RSA OAEP formatter
Dim x_formatter As RSAOAEPKeyExchangeFormatter = New RSAOAEPKeyExchangeFormatter( )
' specify the RSA instance we created as the one
' to use when encrypting the session key data
x_formatter.SetKey(x_rsa)
' encrypt the session key with the formatter
Dim x_exchange_data( ) As Byte = x_formatter.CreateKeyExchange(x_session_key)
' write out the encrypted data
Dim b As Byte
For Each b In x_exchange_data
Console.Write("{0:X2} ", b)
Next
The output below shows an example of the formatted exchange data; the data will change each time the statements are executed, because a new session key will have been created at random by the Rijndael class: C5 3B 7F 1C 44 B9 DF 3C C6 7F 7E 18 3E E7 F9 15 2E 0C B3 2A EF EC 4C EF 45 1D 51 B8 CC D1 FB C8 7F 8B A8 4F 58 92 76 20 61 13 C1 A2 4E 06 50 9A E2 0E 97 34 8A C7 19 8C 21 59 67 30 3A 57 9A E3 B9 4C 5F 56 4F 10 54 1A 83 5B 45 1A 4F 39 A9 C4 64 C2 11 5C 82 6D E1 A9 F3 BD F3 79 87 EA 13 52 2B EF 5F 71 8B 82 08 F6 9D 57 88 43 42 AE 75 E4 DD B9 BD 52 0F DB CD 86 E7 D2 17 0F 2F DB BF A8 The output is sent to the person with whom you wish to exchange confidential messages. Notice that the encrypted session key is much longer than the 20 bytes that make up the 128-bit Rijndael session key; this is a result of the padding added to the session key during the formatting process; the encrypted data is always the same length to prevent revealing the length of the session key to eavesdroppers. Figure 17-6 illustrates the hierarchy for the classes that decrypt the exchange data and restore the session key value by removing the formatting. Figure 17-6. The .NET Framework class hierarchy for key exchange deformatting![]() The process for decrypting a key value is as follows:
Table 17-4 summarizes the members of the abstract AsymmetricExchangeDeformatter class.
The following statements demonstrate how to decrypt the exchange data from the previous example to restore the session key: # C#
byte[] x_exchange_data = new byte[] {0xC5, 0x3B, 0x7F, 0x1C, 0x44, 0xB9, 0xDF,
0x3C, 0xC6, 0x7F, 0x7E, 0x18, 0x3E, 0xE7, 0xF9, 0x15, 0x2E, 0x0C, 0xB3,
0x2A, 0xEF, 0xEC, 0x4C, 0xEF, 0x45, 0x1D, 0x51, 0xB8, 0xCC, 0xD1, 0xFB,
0xC8, 0x7F, 0x8B, 0xA8, 0x4F, 0x58, 0x92, 0x76, 0x20, 0x61, 0x13, 0xC1,
0xA2, 0x4E, 0x06, 0x50, 0x9A, 0xE2, 0x0E, 0x97, 0x34, 0x8A, 0xC7, 0x19,
0x8C, 0x21, 0x59, 0x67, 0x30, 0x3A, 0x57, 0x9A, 0xE3, 0xB9, 0x4C, 0x5F,
0x56, 0x4F, 0x10, 0x54, 0x1A, 0x83, 0x5B, 0x45, 0x1A, 0x4F, 0x39, 0xA9,
0xC4, 0x64, 0xC2, 0x11, 0x5C, 0x82, 0x6D, 0xE1, 0xA9, 0xF3, 0xBD, 0xF3,
0x79, 0x87, 0xEA, 0x13, 0x52, 0x2B, 0xEF, 0x5F, 0x71, 0x8B, 0x82, 0x08,
0xF6, 0x9D, 0x57, 0x88, 0x43, 0x42, 0xAE, 0x75, 0xE4, 0xDD, 0xB9, 0xBD,
0x52, 0x0F, 0xDB, 0xCD, 0x86, 0xE7, 0xD2, 0x17, 0x0F, 0x2F, 0xDB, 0xBF,
0xA8};
// create an instance of the RSA algorithm class
RSA x_rsa = new RSACryptoServiceProvider( );
//
// ... specify private key to decrypt with
//
// create a new instance of the RSA OAEP deformatter
RSAOAEPKeyExchangeDeformatter x_deformatter
= new RSAOAEPKeyExchangeDeformatter( );
// specify the RSA instance we created as the one
// to use when encrypting the session key data
x_deformatter.SetKey(x_rsa);
// decrypt the exchange data with the deformatter to
// obtain the Rijndael session key
byte[] x_session_key = x_deformatter.DecryptKeyExchange(x_exchange_data);
// create an instance of the Rijndael algorithm
Rijndael x_rijndael = Rijndael.Create( );
// set the session key
x_rijndael.Key = x_session_key;
# Visual Basic .NET
Dim x_exchange_data( ) As Byte = New Byte( ) {&HC5, &H3B, &H7F, &H1C, &H44, &HB9, _
&HDF, &H3C, &HC6, &H7F, &H7E, &H18, &H3E, &HE7, &HF9, &H15, &H2E, &HC, &HB3, _
&H2A, &HEF, &HEC, &H4C, &HEF, &H45, &H1D, &H51, &HB8, &HCC, &HD1, &HFB, _
&HC8, &H7F, &H8B, &HA8, &H4F, &H58, &H92, &H76, &H20, &H61, &H13, &HC1, _
&HA2, &H4E, &H6, &H50, &H9A, &HE2, &HE, &H97, &H34, &H8A, &HC7, &H19, _
&H8C, &H21, &H59, &H67, &H30, &H3A, &H57, &H9A, &HE3, &HB9, &H4C, &H5F, _
&H56, &H4F, &H10, &H54, &H1A, &H83, &H5B, &H45, &H1A, &H4F, &H39, &HA9, _
&HC4, &H64, &HC2, &H11, &H5C, &H82, &H6D, &HE1, &HA9, &HF3, &HBD, &HF3, _
&H79, &H87, &HEA, &H13, &H52, &H2B, &HEF, &H5F, &H71, &H8B, &H82, &H8, _
&HF6, &H9D, &H57, &H88, &H43, &H42, &HAE, &H75, &HE4, &HDD, &HB9, &HBD, _
&H52, &HF, &HDB, &HCD, &H86, &HE7, &HD2, &H17, &HF, &H2F, &HDB, &HBF, _
&HA8}
' create an instance of the RSA algorithm class
Dim x_rsa As RSA = New RSACryptoServiceProvider( )
'
' ... specify private key to decrypt with
'
' create a new instance of the RSA OAEP deformatter
Dim x_deformatter As RSAOAEPKeyExchangeDeformatter _
= New RSAOAEPKeyExchangeDeformatter( )
' specify the RSA instance we created as the one
' to use when encrypting the session key data
x_deformatter.SetKey(x_rsa)
' decrypt the exchange data with the deformatter to
' obtain the Rijndael session key
Dim x_session_key( ) As Byte = x_deformatter.DecryptKeyExchange(x_exchange_data)
' create an instance of the Rijndael algorithm
Dim x_rijndael As Rijndael = Rijndael.Create( )
' set the session key
x_rijndael.Key = x_session_key
In Chapter 15, we demonstrated that the RSA implementation class always prepares data by applying either OAEP or PKCS #1 v1.5 formatting, making its functionality equivalent to using the formatting classes discussed in this section. These classes define a generic approach to key exchange that can be applied to all implementation classes, including those provided by third parties. In the next section, we define the formatting classes for our ElGamal implementation, which does not format data during encryption automatically. |
| [ Team LiB ] |
|