tsJensen

A quest for software excellence...

Very Large Pseudo Random Number Generator in C#

Thanks to a friend of mine, over the past few weeks the idea of zero knowledge authentication, which has been around for a long time but to which I’d never paid any attention, has been percolating in my head. Finally on Thursday (Thanksgiving holiday here in the U.S.), I dove in and added it to ServiceWire.

Now you can secure your ServiceWire RPC calls without PKI or exchanging keys or exposing a password across the wire. It validates on the server that the client knows the secret key (password) and on the client that the server knows the secret key. It creates a shared encryption key that is used for that one connection session and is never passed across the wire, allowing all subsequent communications between client and server to be encrypted with strong and fast symmetric encryption, denying any man in the middle from access to the data or even the service definition.

But this post is not about the entire zero knowledge addition to ServiceWire. Instead I want to share one part of that addition which you may use to create a very large random number. In this case, 512 bytes (that’s 4096 bits for those of you who can only count to 1). If you want a larger random number, you’ll need a larger safe prime from a Sophie Germain prime. In ServiceWire, I only include the 512 byte safe prime. There are a number of sources for larger safe primes freely available on the web.

This is not an algorithm entirely of my invention. It is based on several different algorithms I studied while investigating zero knowledge secure remote password protocols. I did take some liberties by adding a randomly selected smaller safe prime as the exponent in the algorithm.

And here’s the code:

using System.Numerics;

public class ZkProtocol
{
   private readonly SHA256 _sha;
   private readonly Random _random;
   private readonly BigInteger _n;

   public ZkProtocol()
   {
      _sha = SHA256.Create(); //used in other protocol methods
      _random = new Random(DateTime.Now.Millisecond);
      _n = new BigInteger(ZkSafePrimes.N4);
   }
   
   //...other protocol methods excluded for post

   /// <summary>
   /// Generate crypto safe, pseudo random number.
   /// </summary>
   /// <param name="bits">max value supported is 4096</param>
   /// <returns></returns>
   public byte[] CryptRand(int bits = 4096)
   {
      var rb = new byte[256];
      _random.NextBytes(rb);
      var bigrand = new BigInteger(rb);
      var crand = BigInteger.ModPow(bigrand, 
         ZkSafePrimes.GetSafePrime(_random.Next(0, 2047)), _n);
      var bytes = crand.ToByteArray();
      if (bits >= 4096) return bytes;
      var count = bits / 8;
      var skip = _random.Next(0, bytes.Length - count);
      return bytes.Skip(skip).Take(count).ToArray();
   }
}

internal static class ZkSafePrimes
{
   internal static byte[] N4
   {
      get { return Safe4096BitPrime; }
   }

   private static byte[] Safe4096BitPrime = new byte[]
   {
      //data not shown here for post (see on GitHub)
   };
   
   public static int GetSafePrime(int index)
   {
      if (index < 1) index = 1;
      else if (index > 2047) index = 2047;
      else if (index % 2 == 0) index++;
      return _safePrimes[index];
   }

   //2048 values, every second value is a safe primes 
   //the previous value is the Sophie Germain prime
   //as in q is the Sophie Germain and N = 2q+1 is safe
   private static int[] _safePrimes = new[]
   {
      5903,11807,8741,17483 //first four value only - see GitHub
   };
}

See the code on GitHub. Look specifically at the ZkProtocol and ZkSafePrimes classes.

I should note that in ServiceWire a new instance of ZkProtocol is used for each connection, so the Random object should provide a healthy random seed to the algoritm given the clock's milliseconds seed on construction. If you find this useful, I would love to hear from you.