The encryption algorithms discussed in Chapter 2 require a source of random data to generate new symmetric keys. Most computers do not have a hardware-based random number generator, so software developers need to use a software-based implementation to generate random numbers that are suitable.
Because random numbers are generated in software, they are rarely completely random; they are typically pseudorandom ; that is, they appear random, but are not random. To create random data, you need a source of entropy or random input.
Modern cryptographic algorithms are built around Kerckhoff’s principle, which states that the “security of the system must depend solely on the key material, and not on the design of the system.” This means that the strength of modern cryptographic algorithms is determined by the number of bits an attacker needs to guess to break the algorithm. In this book, for example, we use 256-bit keys (32 bytes) with AES and 2048- and 4096-bit keys with RSA. The strength also implies that a potential attacker does not know of any of the bits used in the encryption keys. The strength of a key and algorithm starts to diminish when new attacks against the algorithm are found, and any of the keys can be derived by looking at the encrypted output.
Some random-number generators ask you to move your mouse or use your keyboard as a source of that entropy; others take events such as hard-drive activity or network activity for their source of entropy. Entropy is a measure of uncertainty or randomness associated with data. Creating entropy through physical movements typically works well for computer workstations but can be a problem in some cases, such as server hardware with a network card, some Flash RAM, and CPUs. A network appliance that performs encryption has no good sources of entropy, apart from network events, which an attacker could manipulate to their advantage. Before we look at implementation of a reliable random number generator, let’s take a look at the common System.Random class that is in .NET.
Generating Deterministic Random Numbers
Before becoming familiar with cryptography in .NET, you might have used the random number generator in the System namespace. This is fine for simple scenarios like creating lottery numbers or simulating a dice roll, because it gives the appearance of randomness if you provide a different seed value every time. If you provide the same initial seed value, you will get the same numbers out of the random number generator when you run it. This deterministic nature is no good for generating secure cryptographic random numbers.
If you provide the same seed value into different random-number generator instances, then you will generate the same set of random numbers. If creating a deterministic series of numbers is what you require, then this way of using System.Random is perfectly fine. You may need to generate deterministic test data or example for your unit tests. If you wanted to ensure that you create different random numbers each time, then you should make your seed value time-dependent, which means that you are guaranteed a different seed value every time.
While using a time-variant seed value offers more random and non-deterministic numbers, it is not recommended for generating random numbers suitable for cryptography. Another issue with System.Random is that it is not thread safe; so if you are using System.Random, you need to use thread locking around each call. Let’s not dwell on System.Random any longer, and instead look at a better solution, which is RNGCryptoServiceProvider.
Generating Secure Random Numbers
As you have seen, random numbers are critical in using cryptography efficiently because we need good, non-deterministic random numbers to create symmetric and asymmetric encryption keys.
System.Random is not very good at generating non-deterministic random numbers. To produce cryptographically secure random numbers, the RNGCryptoServiceProvider class uses an internal Windows cryptographic service provider to create the number. RNGCryptoServiceProvider is a much safer way of generating these numbers, and it is the technique that Microsoft recommends. The trade-off for securer random numbers is that RNGCryptoServiceProvider is much slower to execute compared to System.Random, but this is a small trade-off when you want to deal with numbers being generated to use as encryption keys.
The current running process ID
The current thread ID
A tick counter from since the time the machine was rebooted
The current time
High-precision performance counters
A hash of user data, such as username, computer name, and so forth
Internal high-precision timers
As you can see, making use of RNGCryptoServiceProvider is quite straightforward. The preceding method, GenerateRandomNumber, takes an integer that represents the length of the random number we want to generate. This length is the number of bytes we want to produce. If you require a 256 bit random number, then you would pass in 32, which is 32 bytes. Next, we create an instance of the RNGCryptoServiceProvider class, and then we create a new byte array that is initialized to the length we passed into the GenerateRandomNumber method. Now we call the GetBytes method on the RNGCryptoServiceProvider class instance and pass in the byte array that we just created. This fills that byte array with random data, which is then returned from the method.
The random numbers we have generated are all 32 bytes in length, which is 256 bits. This is the size of the random number that we use for symmetric encryption later in the book. At a casual glance, you can see that each of the numbers is entirely different, which is what we want for effective cryptography.
Summary
Good quality, non-deterministic, random numbers are one of the essential primitives we will look at in this book. Without genuinely random numbers, the security of our cryptographic algorithms falls apart and becomes susceptible to attackers. We first looked at the System.Random number generator in .NET, which is fine for generating simple lottery numbers or dice rolls, but it is not suitable for cryptography because it is not random enough. The solution recommended by Microsoft is to use RNGCryptoServiceProvider because it generates higher quality, non-deterministic numbers. In the next chapter, we look at our next cryptographic primitive, hashing, which helps satisfy the first pillar—integrity.