Security is an implicit requirement in practically every software application. As a software developer, it is very important to be aware of the security implications of any code that you write, ensuring that you are protecting users and their data.
Many applications need to make use of secure random strings for various purposes. One of the most common scenarios is that of API access keys, which need to be very random and cryptographically strong.
Generating random strings (or numbers) is quite a straightforward process in .NET applications. However, it is vital to have knowledge of the types contained within the BCL (Base Class Libraries). Without a solid understanding of the classes and methods that are available, it can be all too easy to implement an insecure solution where a secure one was needed.
When it comes to generating a secure random string, it’s almost as straightforward as creating an insecure one. The most important thing is that you know the correct types to use to achieve the end goal you are seeking.
In this article, I will show you a very concise way of generating a cryptographically secure random string in your .NET application using C#.
What to avoid
Before showing you the secure implementations, I’d like to quickly draw your attention to common insecure solutions.
Random
The Random
class in .NET provides one of the most basic ways of generating random data.
Here’s the code you would use to get a random number between 1 and 100.
var random = new Random(); long number = random.NextInt64(minValue: 1, maxValue: 100); // Output example: Random number: 42 Console.WriteLine("Random number: {0}", number);
By default, the Random
class uses the System Clock to create its seed (i.e. starting value) and uses a deterministic algorithm to generate the random values it produces. As a result, the values that are generated by an instance of the Random
class are predictable and not truly random. Therefore the Random
class is not suitable for generating secure random strings.
The above code generates a random number, not a string. In an earlier blog article, I covered how to generate a random string using the following method.
/// <summary> /// Generates a random alphanumeric string. /// </summary> /// <param name="length">The desired length of the string</param> /// <returns>The string which has been generated</returns> public static string GenerateRandomAlphanumericString(int length = 10) { const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; var random = new Random(); var randomString = new string(Enumerable.Repeat(chars, length) .Select(s => s[random.Next(s.Length)]).ToArray()); return randomString; }
This method works well for its intended purpose, but it should not be used for anything that is security-critical.
What to do
Now onto what you should do instead, let’s take a look at the options we have.
RNGCryptoServiceProvider
Up until .NET 6.0, using an instance of the RNGCryptoServiceProvider
class was the recommended way to generate a truly random sequence of bytes. These bytes could then be converted into a string or number, according to the requirements.
Below is an example of how to use RNGCryptoServiceProvider
to get a random array of bytes and convert the value of these bytes into a Base64 string.
using System.Security.Cryptography;
using (var cryptoProvider = new RNGCryptoServiceProvider()) { byte[] bytes = new byte[64]; cryptoProvider.GetBytes(bytes); string secureRandomString = Convert.ToBase64String(bytes); // Output example: Secure random string: OfGER+tSZIOSz314OlHk1aM+N8oNXDRHqTn3c5EVknYO5b5s0kqq40lJzoGj99ZXCvoFhkNG8KwQQvBPaR0FtQ== Console.WriteLine("Secure random string: {0}", secureRandomString); }
As you can see from the above example, with .NET there isn’t much code required to generate a secure string.
RNGCryptoServiceProvider
belongs to the System.Security.Cryptography
namespace and implements the IDisposable
interface. The using
statement ensures that any resources are disposed of correctly.
An empty array of bytes is passed to the GetBytes
method which populates the byte array with random values. A byte array length of 64 will result in 512-bits of data.
Lastly, the Convert.ToBase64String
method is called to convert the bytes into a Base64 string and the value is output to the console for inspection.
RandomNumberGenerator
Following the release of .NET 6.0, Microsoft has now marked the RNGCryptoServiceProvider
class as obsolete. It is now recommended to use the static methods of the RandomNumberGenerator
class instead.
The reason for this is due to the RNGCryptoServiceProvider
implementation not being supported on all platforms. Since the ethos of .NET Core is to be cross-platform friendly, as a best practice the following code should be used.
using System.Security.Cryptography;
string secureRandomString = Convert.ToBase64String(RandomNumberGenerator.GetBytes(64)); // Output example: Secure random string: Hn5mduOte7yfFKaHEqj5WD5dU/Wluag76k6Fte/AVOiLrPuQxXB8piT0iauDTcbigcCDyl6udyQpCaazYyRH7Q== Console.WriteLine("Secure random string: {0}", secureRandomString);
In addition to working on multiple platforms, the above code has the added benefit of being much for concise.
I recommend wrapping this code in your own method to make things even simpler.
using System.Security.Cryptography;
/// <summary> /// Creates a cryptographically secure random key string. /// </summary> /// <param name="count">The number of bytes of random values to create the string from</param> /// <returns>A secure random string</returns> public static string CreateSecureRandomString(int count = 64) => Convert.ToBase64String(RandomNumberGenerator.GetBytes(count));
string secureRandomString = CreateSecureRandomString();
Despite the underlying complexity of how the random numbers are generated, .NET makes it really straightforward and convenient for developers to use cryptographic methods like those provided by the RandomNumberGenerator
class.
Storage
If you’re planning to use the randomly generated string as a key (e.g. an API access key) you’ll need to consider how you are going to store the key. As with any type of credential such as a password, or in this case a key, you’ll need to make sure you are employing a cryptographically secure hashing algorithm.
Passwords
For user passwords, it is vitally important that you use an industry-standard password-based key derivation function, such as scrypt. Doing so will ensure that passwords have the proper level of security and are stored with a randomly generated salt value that further enhances the security.
Access keys
API access keys, on the other hand, are different in their nature to passwords. Yes, they are still credentials that are used to access potentially sensitive data. However, they are, or at least ought to be, much longer and more random than user passwords.
Hashing
Consider the example output below which was generated by the RandomNumberGenerator
code that was demonstrated in the previous section.
Hn5mduOte7yfFKaHEqj5WD5dU/Wluag76k6Fte/AVOiLrPuQxXB8piT0iauDTcbigcCDyl6udyQpCaazYyRH7Q==
This is a very long and complex key. Having said that, we certainly would not want to store this key in a file or database in plain text format.
Let’s convert the key to a SHA512 Base64 string as follows.
using System.Security.Cryptography;
using System.Text;
string secureRandomString = CreateSecureRandomString();
using (var sha = SHA512.Create()) { var bytes = Encoding.UTF8.GetBytes(secureRandomString); var hash = sha.ComputeHash(bytes); var hashString = Convert.ToBase64String(hash); // Output: Hash string: VdI8w9UnTdTetW2VvVWZtzLKrvxLWxc9ciLuBxDdB5Pc+9sSLnTW7eu21KK7Ht0pbErmtyo59XG/2XPJinSIZg== Console.WriteLine("Hash string: {0}", hashString); }
As indicated by the comment in the above code, the hash string is as follows and is completely unrecognisable compared to the original string.
VdI8w9UnTdTetW2VvVWZtzLKrvxLWxc9ciLuBxDdB5Pc+9sSLnTW7eu21KK7Ht0pbErmtyo59XG/2XPJinSIZg==
I’m not sure about you, but I can’t imagine a 512-bit hash of a value as long and as random as the example shown further above being cracked very easily. In short, given the parameters being used, adding a salt value would not provide any additional benefit to the security of this API key.
To make things simpler, you can turn the above code into an extension method, as follows.
using System.Security.Cryptography;
/// <summary> /// Computes a SHA512 hash for a string and returns the hash as a Base64 encoded string. /// </summary> /// <param name="value">The string to operate on</param> /// <returns>A SHA512 Base64 encoded string</returns> public static string ToSHA512(this string value) { using var sha = SHA512.Create(); var bytes = Encoding.UTF8.GetBytes(value); var hash = sha.ComputeHash(bytes); return Convert.ToBase64String(hash); }
string secureRandomString = CreateSecureRandomString();
string hashString = secureRandomString.ToSHA512();
The ToSHA512
method can now be called on any string value to conveniently compute its SHA512 hash and convert this into a Base64 string for storage.
Summary
In this article, I have focused on how to leverage the power of .NET to create a cryptographically strong sequence of bytes and how to convert these bytes into a secure random string of characters.
I started by looking at what you should not do and advised against the use of the Random
class for anything that is security-critical.
I then walked through how to use the RNGCryptoServiceProvider
and RandomNumberGenerator
classes in the .NET Framework and .NET Core respectively, to create random numbers and strings securely.
Lastly, I discussed some of the considerations around the security of the strings at rest, touching on both scrypt and the SHA512 algorithm.
Comments