Search code examples
c#.netcryptographyecdsaelliptic-curve

Reproducible Elliptic Curve Digital Signature algorithm parameters in C#


Using the following code, I can generate the Elliptic Curve Digital Signature Algorithm (ECDSA) parameters (i.e., public and private keys).

using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP384);
var publicKey = ecdsa.ExportParameters(false);
var privateKey = ecdsa.ExportParameters(true);

This method generates new keys every time, which is the correct choice for many applications. However, in my application, I need to be able to fix the random seed to get reproducible keys.

I have two requirements:

  • Using only the built-in methods;
  • Cross-platform support.

Any suggestions on how I can generate the reproducible keys?


Solution

  • The raw private EC key is a random byte sequence, e.g. for P-384 consisting of 48 bytes. Analogous to the key derivation of a symmetric key, a key derivation function can be used to determine the same raw private key based on a given key material.
    If the key material is a passphrase of insufficient entropy, a password-based key derivation function (such as PBKDF2) should be used in conjunction with a (non-secret) random salt. The raw private key can then be reconstructed using the passphrase and the salt.
    If the key material has sufficient entropy, a hash-based key derivation function (HKDF) can be used (see RFC 5869). NET provides an implementation for this as of .NET 5, see HKDF.

    The public key (or the corresponding ECPoint) is obtained from the raw private key by multiplying it by the generator point of the respective curve. However, it is not necessary to determine the public key directly, as .NET determines it implicitly under the hood (should direct determination be necessary for some reason: as far as I know, .NET does not expose public methods for modular arithmetic, meaning that other libraries would have to be used (e.g. BouncyCastle) to determine the raw public key directly from the raw private key).

    As the key material has sufficient entropy according to the question, the following is an example implementation with HKDF. This does not require any additional libraries and works cross platform:

    private static ECDsa CreateKeyViaHKDF(byte[] keyMaterial, ECCurve ecCurve, int size)
    {
        ECParameters ecp = new ECParameters();
        ecp.Curve = ecCurve;
        ecp.D = HKDF.DeriveKey(HashAlgorithmName.SHA512, keyMaterial, size, null, null);
        return ECDsa.Create(ecp); 
    }
    

    Test:

    using System;
    using System.Security.Cryptography;
    ...
    byte[] keyMaterial = Convert.FromHexString("d25a4ea14eb6ad223393fd84ab59a62e"); // keyMaterial with sufficient entropy
    ECDsa key = CreateKeyViaHKDF(keyMaterial, ECCurve.NamedCurves.nistP384, 48);
    
    // Export raw private/public key
    ECParameters privateKey = key.ExportParameters(true);
    ECParameters publicKey = key.ExportParameters(false);
    Console.WriteLine("Private key (raw, Base64):   " + Convert.ToBase64String(privateKey.D));
    Console.WriteLine("Public key, X (raw, Base64): " + Convert.ToBase64String(publicKey.Q.X));
    Console.WriteLine("Public key, Y (raw, Base64): " + Convert.ToBase64String(publicKey.Q.Y));
    
    // Export keys in PKCS#8 (private key) and SPKI (public key) format 
    Console.WriteLine("\nPrivate key (PKCS#8): " + key.ExportPkcs8PrivateKeyPem());
    Console.WriteLine("\nPublic key (SPKI): " + key.ExportSubjectPublicKeyInfoPem());