1. Creating Keys
In 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.
1.1. Using the algorithm classes
The 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:
# 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 :
# 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.
When using an algorithm class to create a new key, you must be
confident that the implementation creates keys securely and there are
no flaws in the implementation that might result in key values that
are easily guessed.
|
|
1.2. Using a random number generator
The second approach to creating keys
is to use
a random number generator (RNG). The .NET Framework provides support
classes for generating random data; Figure 1
illustrates the class hierarchy.
You cannot generate random data to create a key pair for an
asymmetric algorithm. You must rely on the implementation classes to
create key pairs or follow the appropriate key generation protocol. Asymmetric algorithms
create keys using numbers that are mathematically related and cannot
be created from random data.
|
|
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 1 details the
public members of the abstract
RandomNumberGenerator class.
Table 1. Members of the RandomNumberGenerator class
Member
|
Description
|
---|
Create
|
Creates an instance of a RandomNumberGenerator
implementation class by name or creates an instance of the default
implementation class if no name is specified
|
GetBytes
|
Populates a byte array with a sequence of random values
|
GetNonZeroBytes
|
Populates a byte array with a sequence of random values, none of
which will be 0
|
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
1.3. Using a key-derivation protocol
The 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:
Alice selects a password. The password can be made up of more than
one word—for example, "Programming .NET
Security" would be acceptable as a password; though
the word "password" is used,
"pass phrase" is more appropriate.
Alice generates eight bytes of random data, known as the
salt. The salt value is not secret, and Alice
can safely write it down. The benefit of adding random data to the
password is that two users who select the same password will end up
with different cryptographic keys.
Alice concatenates her password and the salt data together to form
the data block.
Alice selects a hashing algorithm and creates a hash code for the
data block.
For a specified number of iterations, Alice uses the hashing
algorithm to create a new hash code, using the previous hash code as
the input. The first hash code is produced by hashing the data block.
The second hash code is produced by hashing the first hash code. The
third hash code is created by hashing the second hash code, and so
on. It is common to perform 100 iterations.
Alice uses the first n bytes of the final hash
code to create a cryptographic key of 8n bits. For
example, if Alice selected the SHA-1 hashing algorithm, she will
acquire a hash code that is 20 bytes long (because SHA-1 produces
160-bit hash codes). If Alice required a 128-bit key, she will use
the first 16 bytes of the hash code value.
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 2 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 2.
Table 17-2. PasswordDeriveBytes Members
Member
|
Description
|
---|
Properties
| |
HashName
|
The name of the hashing algorithm to use;
|
IterationCount
|
The number of iterations to perform to create the final hash code
|
Salt
|
A byte array representing the salt data
|
Methods
| |
GetBytes
|
Creates the final hash code and returns a number of bytes, specified
as an integer method argument
|
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( )