Cryptography Next Generation (CNG) for ECDSA

I know the title sounds so interesting, but this article is just plain old vanilla ECDSA digital signing. But it is a good example of…

Cryptography Next Generation (CNG) for ECDSA

I know the title sounds so interesting, but this article is just plain old vanilla ECDSA digital signing. But it is a good example of showing the key elements of a digital signature. So what is CNG? Well, CNG is Microsoft’s attempt at unbundling OpenSSL from the Windows operating system and producing core .NET code that will run only on a Windows machine.

Overall, Microsoft has tended to be rather slow in adopting new cryptography methods. CNG was released in 2021 and is seen as a direct long-term replacement for the rather cumbersome CryptoAPI. The core classes are defined with a CNG in the name, such as:

ECDiffieHellmanCng
ECDsaCng

When compiled, the CNG class will get a warning message. For example, with ECDsaCng() we get:

This call site is reachable on all platforms. 
'ECDsaCng' is only supported on: 'windows'.

Thus, if you need your code to be compatible with Mac OS and Linux systems, you would probably avoid using CNG.

Method

With Elliptic Curve Cryptography (ECC) we can use a Weierstrass curve form of the form of y²=x³+ax+b (mod p) . Bitcoin and Ethereum use secp256k1 and which has the form of =x³+7 (mod p). In most cases, though, we use the NIST-defined curves. These are SECP256R1, SECP384R1, and SECP521R1, but we also use SECP224R1 and SECP192R1. SECP256R1(NIST P256) has 256-bit (x,y) points, and where the private key is a 256-bit scalar value (a) and which gives a public key point of:

a.G

Creating the signature

An outline of ECDSA is:

Code

First we create a folder named “bc_ecdsa_cng, and then go into that folder. We can create a Dotnet console project for .NET 8.0 with:

dotnet new console --framework net8.0

Initially Bob has a message to sign, he must first convert it to a byte array with:

var msg=Encoding.ASCII.GetBytes(message);

Next we can create a CNG object for Bob using a given curve (curvename):

var c =  ECCurve.CreateFromFriendlyName(curvename);


var bob = new ECDsaCng(c);

We can then take a SHA-256 hash of the message and produce the signature with [here]:



var h1=SHA256.Create();
ar res= h1.ComputeHash(msg);

var sighash=bob.SignHash(res);

This signature will have the (r, s) values. We can display these with [here]:

var r=Convert.ToHexString(sighash[0..(sighash.Length/2)]);
var s=Convert.ToHexString(sighash[(sighash.Length/2)..sighash.Length]);

Console.WriteLine("\nSignature (r,s)):\t{0}, {1}",r,s);

Now, Bob sends the message and signature to Alice, along with his exported public key [here]:

var bobPublic = bob.ExportSubjectPublicKeyInfoPem();

Alice receives Bob’s public key, and check the message hash with the signature [here]:

alice.ImportFromPem(bobPublic);
var rtn=alice.VerifyHash(res,sighash);

If rtn is true, the signature checks out.

Displaying keys

If Bob has a private key of D and a point key point of Q, we can display these in several forms, including as hex values, a PEM format, and a DER format [here]:

var b =  bob.ExportParameters(true);
Console.WriteLine("\n\nBob Private Key D:\t\t{0}",Convert.ToHexString(b.D));
Console.WriteLine("Bob Private Key (DER):\t\t{0}",Convert.ToHexString(bob.ExportPkcs8PrivateKey()));
Console.WriteLine("Bob Private Key (PEM):\n{0}",bob.ExportECPrivateKeyPem());

Console.WriteLine("\nBob Public Key Q:\t\t({0}, {1})",Convert.ToHexString(b.Q.X),Convert.ToHexString(b.Q.Y));
Console.WriteLine("Bob Public Key (DER):\t\t{0}",Convert.ToHexString(bob.ExportSubjectPublicKeyInfo()));
Console.WriteLine("Bob Public Key (PEM):\n{0}",bob.ExportSubjectPublicKeyInfoPem());

The full code is [here]:

namespace CngECDSA
{
using System.Security.Cryptography;
using System.Text;
class Program
{

static void Main(string[] args)

{

var curvename="nistP256";
var h=CngAlgorithm.Sha256;
var hashmethod="SHA256";
var message="Hello";
try {
if (args.Length >0) message=args[0];
if (args.Length >1) curvename=args[1];
if (args.Length >2) hashmethod=args[2];

var c = ECCurve.CreateFromFriendlyName(curvename);
var msg=Encoding.ASCII.GetBytes(message);

var bob = new ECDsaCng(c);

var h1=SHA256.Create();
var res= h1.ComputeHash(msg);
if (hashmethod=="SHA256") { var h2=SHA256.Create(); res= h2.ComputeHash(msg); }
if (hashmethod=="MD5") { var h3=MD5.Create(); res= h3.ComputeHash(msg); }
if (hashmethod=="SHA1") { var h4=SHA1.Create(); res= h4.ComputeHash(msg); }
if (hashmethod=="SHA384") { var h5=SHA384.Create(); res= h5.ComputeHash(msg); }
if (hashmethod=="SHA512") { var h6=SHA512.Create(); res= h6.ComputeHash(msg); }


var sighash=bob.SignHash(res);
var alice = new ECDsaCng();
var bobPublic = bob.ExportSubjectPublicKeyInfoPem();
alice.ImportFromPem(bobPublic);
var rtn=alice.VerifyHash(res,sighash);


Console.WriteLine("Message:\t{0}",message);
Console.WriteLine("Hash:\t\t{0}",Convert.ToHexString(res));
Console.WriteLine("Curve:\t\t{0}",curvename);
Console.WriteLine("Hash:\t\t{0}",hashmethod);
var r=Convert.ToHexString(sighash[0..(sighash.Length/2)]);
var s=Convert.ToHexString(sighash[(sighash.Length/2)..sighash.Length]);

Console.WriteLine("\nSignature (r,s)):\t{0}, {1}",r,s);
Console.WriteLine("Verified:\t\t{0}",rtn);


var b = bob.ExportParameters(true);
Console.WriteLine("\n\nBob Private Key D:\t\t{0}",Convert.ToHexString(b.D));
Console.WriteLine("Bob Private Key (DER):\t\t{0}",Convert.ToHexString(bob.ExportPkcs8PrivateKey()));
Console.WriteLine("Bob Private Key (PEM):\n{0}",bob.ExportECPrivateKeyPem());

Console.WriteLine("\nBob Public Key Q:\t\t({0}, {1})",Convert.ToHexString(b.Q.X),Convert.ToHexString(b.Q.Y));
Console.WriteLine("Bob Public Key (DER):\t\t{0}",Convert.ToHexString(bob.ExportSubjectPublicKeyInfo()));
Console.WriteLine("Bob Public Key (PEM):\n{0}",bob.ExportSubjectPublicKeyInfoPem());



} catch (Exception e) {
Console.WriteLine("Error: {0}",e.Message);
}
}
}
}

A sample run for NIST P256 gives [here]:

Message:	Hello World!
Hash: 7F83B1657FF1FC53B92DC18148A1D65DFC2D4B1FA3D677284ADDD200126D9069
Curve: nistP256
Hash: SHA256

Signature (r,s)): BC37DFD5737E3E3F43E3F635639C13B7626F1BC072B3524E1B2A92A7C616F2E3, AFD77CF1C7AF81984DA7F659D3A39E264F5C5074968211DAA1DBED92BE8C37A9
Verified: True

Bob Private Key D: 85779B1292E4EC6ABBD26B237A9E4BC7A6563A8788909794C3D1BB61374C7EBA
Bob Private Key (DER): 3081A2020100301306072A8648CE3D020106082A8648CE3D03010704793077020101042085779B1292E4EC6ABBD26B237A9E4BC7A6563A8788909794C3D1BB61374C7EBAA00A06082A8648CE3D030107A14403420004A279736886AEC3353E2192A53BC955FFEDA5DDCD12F153E8ACA3852E0CCC68435D51D260177061E44899DD68E1FC3AC9C9BF58E038083FB77DA2FB0529FF3FAEA00D300B0603551D0F310403020080
Bob Private Key (PEM):
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIIV3mxKS5Oxqu9JrI3qeS8emVjqHiJCXlMPRu2E3TH66oAoGCCqGSM49
AwEHoUQDQgAEonlzaIauwzU+IZKlO8lV/+2l3c0S8VPorKOFLgzMaENdUdJgF3Bh
5EiZ3Wjh/DrJyb9Y4DgIP7d9ovsFKf8/rg==
-----END EC PRIVATE KEY-----
Bob Public Key Q: (A279736886AEC3353E2192A53BC955FFEDA5DDCD12F153E8ACA3852E0CCC6843, 5D51D260177061E44899DD68E1FC3AC9C9BF58E038083FB77DA2FB0529FF3FAE)
Bob Public Key (DER): 3059301306072A8648CE3D020106082A8648CE3D03010703420004A279736886AEC3353E2192A53BC955FFEDA5DDCD12F153E8ACA3852E0CCC68435D51D260177061E44899DD68E1FC3AC9C9BF58E038083FB77DA2FB0529FF3FAE
Bob Public Key (PEM):
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEonlzaIauwzU+IZKlO8lV/+2l3c0S
8VPorKOFLgzMaENdUdJgF3Bh5EiZ3Wjh/DrJyb9Y4DgIP7d9ovsFKf8/rg==
-----END PUBLIC KEY-----

Conclusions

The main things to learn from this? There’s a public key, a hashing method, a private key, and a signature (r,s), and they all come together to create a world of digital trust. Try it here:

https://asecuritysite.com/csharp/dotnet_ecdsa

and for ECDH key exchange:

https://asecuritysite.com/csharp/dotnet_ec