Blog | G5 Cyber Security

PBKDF2 with RNGCryptoServiceProvider: Stronger Keys

TL;DR

Using PBKDF2 (Password-Based Key Derivation Function 2) on a key generated by RNGCryptoServiceProvider significantly improves security. RNGCryptoServiceProvider creates random keys, but they’re vulnerable to brute-force attacks if compromised. PBKDF2 adds salt and iteration counts, making cracking the key much harder even if an attacker gets hold of it.

Why Use PBKDF2 with RNGCryptoServiceProvider?

RNGCryptoServiceProvider is a good source of random numbers for generating keys. However, simply storing this raw key isn’t enough in most real-world scenarios. Here’s why:

PBKDF2 addresses these issues by:

Step-by-Step Guide

  1. Generate a Random Key with RNGCryptoServiceProvider:
    using System; 
    using System.Security.Cryptography;
    
    public class Example {
     public static byte[] GenerateRandomKey(int keyLength) {
       byte[] key = new byte[keyLength];
       using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider()) {
         rng.GetBytes(key);
       }
       return key;
     }
    }
    
  2. Generate a Random Salt: The salt should be at least 16 bytes long.
    public static byte[] GenerateRandomSalt(int saltLength) {
      byte[] salt = new byte[saltLength];
      using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider()) {
        rng.GetBytes(salt);
      }
      return salt;
    }
    
  3. Derive the Key using PBKDF2: This is where the magic happens.
    using System.Security.Cryptography; 
    
    public static byte[] DeriveKey(byte[] password, byte[] salt, int iterationCount, int keyLength) {
      using (var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterationCount)) {
        return pbkdf2.GetBytes(keyLength);
      }
    }
    

    Important: Choose a high iteration count (e.g., 10,000 or higher). The higher the count, the more secure, but also the slower the process.

  4. Store the Salt and Derived Key: Store both the salt *and* the derived key in your database.

    Do not store the original password!

  5. Verification (when a user logs in):
    • Retrieve the stored salt for the user.
    • Hash the entered password using PBKDF2 with the retrieved salt and iteration count.
    • Compare the resulting hash to the stored derived key. If they match, the password is correct.

Example Code (Complete)

using System; 
using System.Security.Cryptography;

public class Example {
 public static byte[] GenerateRandomKey(int keyLength) {
   byte[] key = new byte[keyLength];
   using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider()) {
     rng.GetBytes(key);
   }
   return key;
 }

 public static byte[] GenerateRandomSalt(int saltLength) {
  byte[] salt = new byte[saltLength];
  using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider()) {
    rng.GetBytes(salt);
  }
  return salt;
}

 public static byte[] DeriveKey(byte[] password, byte[] salt, int iterationCount, int keyLength) {
  using (var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterationCount)) {
    return pbkdf2.GetBytes(keyLength);
  }
 }

 public static bool VerifyPassword(string password, byte[] storedSalt, byte[] storedDerivedKey, int iterationCount) {
   byte[] derivedKey = DeriveKey(System.Text.Encoding.UTF8.GetBytes(password), storedSalt, iterationCount, storedDerivedKey.Length);
   return CompareByteArrays(derivedKey, storedDerivedKey);
 }

 private static bool CompareByteArrays(byte[] arr1, byte[] arr2) {
  if (arr1.Length != arr2.Length) return false;
  for (int i = 0; i < arr1.Length; i++) {
    if (arr1[i] != arr2[i]) return false;
  }
  return true;
 }

 public static void Main(string[] args) {
   int keyLength = 32; // Example key length
   int saltLength = 16; 
   int iterationCount = 10000;

   byte[] password = System.Text.Encoding.UTF8.GetBytes("mySecretPassword");
   byte[] salt = GenerateRandomSalt(saltLength);
   byte[] derivedKey = DeriveKey(password, salt, iterationCount, keyLength);

   // Store salt and derivedKey in your database.

   bool isValid = VerifyPassword("mySecretPassword", salt, derivedKey, iterationCount);
   Console.WriteLine("Password is valid: " + isValid);
 }
}

Important Considerations

Exit mobile version