Getting down with Public-Key Cryptography pattern and Ecliptic Curves!

You have probably been introduced at some point to cryptography, but perhaps still haven't had the chance to implement the technology. I'll stick the basics of getting you up and running with signing messages and also verifying the sender of the message utilizing Elliptic Curve cryptography inside .NET.

Cryptography

Introduction

When utilizing EventGrids inside an DMZ Zone, it can be useful to have a control mechanism, where our consumer can confirm that the message is actually originated from the authentic publisher.

In order to achieve this, we can utilize the public-key pattern where we generate a public/private keyset where we can sign and verify signatured.

The private key is used to sign the message together with the content of the message it self.

The public key is used to verify the authenticity of the message received (typically the public key is fetched via an API call made to the publisher to fetch the correct correspondent public key.

A public key will typically look like this.

{
    "kty": "EC",
    "use": "sig",
    "crv": "P-256",
    "kid": "Test",
    "x": "ASLfeJztpsusdKcshbpxJ3BJnS3WB6_-p2beRdtDH5k",
    "y": "XGHSN3kFR38ygUwHaKVKEZWRl8b0BqwZ0AUq0NVmg7U",
    "alg": "ES256"
}

Sometimes the key or keyset (containing multiple key's) can also come in "pem" format, which is just an Base64 encoded version of the above example, but this is only for the public key the private key is a bit different.

In order to verify an message, we need the x and y value. Potentially we also need the Kid to identify the Id of the key, since multiple might exist in the solution at hand. The other values are used to determine what algorithm and curve should be used.

Generate keys

Let's start by generating a our keyset utilizing .NET. In order to create our keyset we will use .NET's ECDsa class under the Cryptography namespace. 

For this example we will use Ecliptic P256 Curve utilizing ES256 algorythm. 

Before we get starting with generating the keys let's generate a model set, we can use to contain our public key and share it with our consumers of our messages.

private class PublicKeySet
{
    public List<Key> Keys { get; set; } = new List<Key>();
}
private class Key
{
    public string kid { get; set; }
    public string alg { get; set; }
    public string kty { get; set; }
    public string crv { get; set; }
    public string use { get; set; }
    public string x { get; set; }
    public string y { get; set; }
}

Now we can move on to create our service to generate our public and private key.

var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
var keyId = Guid.NewGuid().ToString();
var keys  = new Model();
keys.Keys.Add(new Key
{
    alg        = "ES256",
    kty        = "EC",
    use        = "sig",
    crv        = "P-256",
    kid        = keyId,
    x          = Base64UrlEncode(ecdsa.ExportParameters(false).Q.X),
    y          = Base64UrlEncode(ecdsa.ExportParameters(false).Q.Y),
});
var publicKey  = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(keys)));
var privateKey = Convert.ToBase64String(ecdsa.ExportECPrivateKey());

 

Signing

Now that our keys are up and running, let's create a new model, which we will use to send our messages with.

 public class EventMessage
    {
      public string Message   { get; set; }
      public string Signature { get; set; }
      public string PublicKeyId { get; set; }
    }

As you can see, it's quite a simple model, that we can transfer to whoever needs to read it. Now that we have our model ready it's time to look at signing our messages.

Let's start by creating a method called Signature, which accepts an message string and a privateKey string in pem format.

public byte[] Signature(string msg, string privateKey)
{
    using (var ecdsa = ECDsa.Create())
    {
      var key          = privateKey;
        var messageBytes = Encoding.UTF8.GetBytes(msg);
        ecdsa.ImportECPrivateKey(key, out _);
    
        return ecdsa.SignData(messageBytes, HashAlgorithmName.SHA256);
    }
}

We will call the Signature method with our EventMessage Message and our PrivateKey generated from our generate keys method.

Validate signature

Before we start using our newly created sign method, let's create another method, where we can validate that our message can be verified using the public key from our keypair.

This way we are sure that the messages that we sign, will actually be able to be processed by our receiver.

Our method expect that the public key has been Base64 encoded, but this could also just be passed as our PublicKeyset.

Now we can continue with creating our verify signature method.

public bool VerifySignature(string message, byte[] signature, string publicKey)
{
    byte[] publicKeyAsByterArray = Convert.FromBase64String(publicKey);
  var model = JsonConvert.DeserializeObject<Models.PublicKeySet>(Encoding.UTF8.GetString(publicKeyAsByterArray)); 
    
    using (var ecdsa = ECDsa.Create(new ECParameters
    {
        Curve = ECCurve.NamedCurves.nistP256,
        Q = new ECPoint
        {
            X = Convert.FromBase64String(model.Keys.Single(x => x.kid == "1234567").x),
            Y = Convert.FromBase64String(model.Keys.Single(x => x.kid == "1234567").y)
        },
    }))
    
    return ecdsa.VerifyData(Encoding.UTF8.GetBytes(message), signature, HashAlgorithmName.SHA256);
    
}

That's it we are done, you should now be able to both sign and verify messages utilizing a Public-key cryptography pattern. If you are working within Azure, I would generally recommend looking into utilizing the Azure Keyvault for storing and generating keys.

I hope this was useful for you, if there's any topic you want me to cover please don't hesitate to contact me, I'm always looking to learn and share on more topics that might be relevant.