1

I'm running this code successfully on my Windows machine (Win 10 x64, running dotnet 4.7.2). It generates an EC keypair ("P-256"), hashes the plaintext with SHA-256, signs the hash with the ec private key and verifies the signature against the hashed plaintext with the ec public key.

I'm getting this output so everything works fine:

EC signature curve secp256r1 / P-256 string
dataToSign: The quick brown fox jumps over the lazy dog
* * * sign the plaintext with the EC private key * * *
EC keysize: 256
signature (Base64): cwLBRSt1vtO33tHWcTdx1OTu9lBFXHEJgvdRyDUynLLE5eMakUZUAKLwaJvYoS7NBylx2Zz0+G6dvgJ6xv5qNA==
* * *verify the signature against hash of plaintext with the EC public key * * *
signature verified: True

Now I'm trying to find any online compiler that is been able to run the code. My favorite compiler (https://repl.it/, Mono C# compiler version 6.8.0.123, full code: https://repl.it/@javacrypto/EcSignatureFull#main.cs) is running into this error:

Unhandled Exception:
System.NotImplementedException: The method or operation is not implemented.
  at EcSignatureString.Main () [0x00036] in <13e2ad358a924efc874a89efad35ffe7>:0
[ERROR] FATAL UNHANDLED EXCEPTION: System.NotImplementedException: The method or operation is not implemented.
  at EcSignatureString.Main () [0x00036] in <13e2ad358a924efc874a89efad35ffe7>:0
exit status 1

Using another platform (https://dotnetfiddle.net/, Compiler .net 5, full code: https://dotnetfiddle.net/lSPpjz) is giving this similar error:

Unhandled exception. System.PlatformNotSupportedException: Windows Cryptography Next Generation (CNG) is not supported on this platform.
   at System.Security.Cryptography.ECDsaCng..ctor(Int32 keySize)
   at EcSignatureString.Main()
Command terminated by signal 6

So my question: is there any online compiler available that is been able to run the code?

I assume my question might be a slice off-topic for SO - in this case - is there any other stackexchange-site that would be a better place for my question?

Warning: the following code has no exception handling and is for educational purpose only:

using System;
using System.Security.Cryptography;

class EcSignatureString {
    static void Main() {

    Console.WriteLine("EC signature curve secp256r1 / P-256 string");
    string dataToSignString = "The quick brown fox jumps over the lazy dog";
    byte[] dataToSign = System.Text.Encoding.UTF8.GetBytes(dataToSignString);
    Console.WriteLine("dataToSign: " + dataToSignString);
    try {
        Console.WriteLine("\n* * * sign the plaintext with the EC private key * * *");

        ECDsaCng ecDsaKeypair = new ECDsaCng(256);
        Console.WriteLine("EC keysize: " + ecDsaKeypair.KeySize);

        byte[] hashedData = null;
        byte[] signature = null;
        // create new instance of SHA256 hash algorithm to compute hash
        HashAlgorithm hashAlgo = new SHA256Managed();
        hashedData = hashAlgo.ComputeHash(dataToSign);

        // sign Data using private key
        signature = ecDsaKeypair.SignHash(hashedData);
        string signatureBase64 = Convert.ToBase64String(signature);
        Console.WriteLine("signature (Base64): " + signatureBase64);

        // get public key from private key
        string ecDsaPublicKeyParametersXml = ecDsaKeypair.ToXmlString(ECKeyXmlFormat.Rfc4050);

        // verify
        Console.WriteLine("\n* * *verify the signature against hash of plaintext with the EC public key * * *");
        ECDsaCng ecDsaVerify = new ECDsaCng();
        bool signatureVerified = false;
        ecDsaVerify.FromXmlString(ecDsaPublicKeyParametersXml, ECKeyXmlFormat.Rfc4050);
        signatureVerified = ecDsaVerify.VerifyHash(hashedData, signature);
        Console.WriteLine("signature verified: " + signatureVerified);
        }
        catch(ArgumentNullException) {
            Console.WriteLine("The data was not signed or verified");
        }
    }
}
Michael Fehr
  • 5,827
  • 2
  • 19
  • 40
  • 1
    Microsoft decided to "offload" .NET 5.0 encryption to the OS. This means that not everything is supported everywhere. There is a table showing what is supported where: https://learn.microsoft.com/it-it/dotnet/standard/security/cross-platform-cryptography – xanatos Dec 28 '20 at 09:37
  • 1
    If you want to be multiplatform you have to use `ECDsa.Create(...)` and then work with the object you received. Not sure if everything you need is implemented in the "generic" `ECDsa` – xanatos Dec 28 '20 at 09:41
  • 1
    The `ToXmlString` doesn't work, and it isn't clear how to export a generated key to PEM format. There are some `Import*Pem` to import the key. – xanatos Dec 28 '20 at 09:53
  • You may need to update your operating system and kernel based on xantaos response above. Older versions of many operating systems do not support the latest encryption modes. – jdweng Dec 28 '20 at 09:58
  • @jdweng: thanks for your response. As I need to run my code in an **online compiler environment** I'm not in the position to update the online compiler, sorry. – Michael Fehr Dec 28 '20 at 13:54
  • What machine and version of operating system are you using? Five years ago the industry obsoleted TLS 1.0/1.1 (used for https authentication) due to hacker being able to penetrate the encryption methods and gave user 5 years to upgrade to TLS 1.2/1.3. Finally in June Microsoft released a Security Update disabling 1.0/1.1 on servers. Old versions of operating Systems may not support 1.2/1.3 so you may be forces to upgrade you operating system on the machine. The Kernel also may need upgrading to be compatible with the operating system. – jdweng Dec 28 '20 at 15:03
  • @jdweng: Online compiler 1: https://repl.it/ is giving this information: Mono C# compiler version 6.8.0.123, online compiler 2 https://dotnetfiddle.net/ is running in .net 5 mode (selected on the left side). More information is not available on first sight. Kindly see the accepted answer from xanatos that solved my problem. – Michael Fehr Dec 28 '20 at 15:07

1 Answers1

4

Microsoft has decided that encryption and hashing must be fully delegated to the OS (in .NET Framework it was half and half), so now .NET 5 (and .NET Core) has multiple backends for encryption (for example for ECDsa it has ECDsaCng that uses Windows services and ECDsaOpenSsl for Linux/MacOs that uses OpenSsl (see MSDN)

Now... the solution for your problem is to use the ECDsa class and let it select its backend. There are some problems with it. You can't easily export the keys to xml format, nor you can easily export them to PEM format. You can easily export them to a byte[], and you can easily import them from PEM format. This isn't really a big problem, because rarely you'll need to generate keys, and normally your program receives its keys from an external source, or if it generates them itself, it can save them to binary format to reuse them later.

var dataToSignString = "Hello world!";
var dataToSign = Encoding.UTF8.GetBytes(dataToSignString);

Console.WriteLine("dataToSign: " + dataToSignString);

try
{
    Console.WriteLine("\n* * * sign the plaintext with the EC private key * * *");

    var ecDsaKeypair = ECDsa.Create(ECCurve.NamedCurves.nistP256);

    // Normally here:
    //ecDsaKeypair.ImportFromPem()

    Console.WriteLine("EC keysize: " + ecDsaKeypair.KeySize);

    byte[] hashedData = null;
    byte[] signature = null;
    // create new instance of SHA256 hash algorithm to compute hash
    HashAlgorithm hashAlgo = new SHA256Managed();
    hashedData = hashAlgo.ComputeHash(dataToSign);

    // sign Data using private key
    signature = ecDsaKeypair.SignHash(hashedData);
    string signatureBase64 = Convert.ToBase64String(signature);
    Console.WriteLine("signature (Base64): " + signatureBase64);

    // get public key from private key
    string ecDsaPublicKeyParameters = Convert.ToBase64String(ecDsaKeypair.ExportSubjectPublicKeyInfo());

    // verify
    Console.WriteLine("\n* * *verify the signature against hash of plaintext with the EC public key * * *");

    var ecDsaVerify = ECDsa.Create(ECCurve.NamedCurves.nistP256);
    bool signatureVerified = false;

    // Normally here:
    //ecDsaKeypair.ImportFromPem()
    var publicKey = Convert.FromBase64String(ecDsaPublicKeyParameters);
    ecDsaVerify.ImportSubjectPublicKeyInfo(publicKey, out _);

    signatureVerified = ecDsaVerify.VerifyHash(hashedData, signature);
    Console.WriteLine("signature verified: " + signatureVerified);
}
catch (ArgumentNullException)
{
    Console.WriteLine("The data was not signed or verified");
}

About the From/ToXmlFormat, the current comment on them on the github of .NET Core is:

// There is currently not a standard XML format for ECC keys, so we will not implement the default
// To/FromXmlString so that we're not tied to one format when a standard one does exist. Instead we'll
// use an overload which allows the user to specify the format they'd like to serialize into.

Mmmh from some tests done, exporting in PEM format seems to be quite easy:

public static IEnumerable<string> Split(string str, int chunkSize)
{
    for (int i = 0; i < str.Length; i += chunkSize)
    {
        yield return str.Substring(i, Math.Min(chunkSize, str.Length - i));
    }
}

and then

string b64privateKey = Convert.ToBase64String(ecDsaKeypair.ExportPkcs8PrivateKey());
b64privateKey = string.Join("\r\n", Split(b64privateKey, 64));
string pemPrivateKey = "-----BEGIN PRIVATE KEY-----\r\n" + b64privateKey + "\r\n-----END PRIVATE KEY-----";

or

string b64publicKey = Convert.ToBase64String(ecDsaKeypair.ExportSubjectPublicKeyInfo());
b64publicKey = string.Join("\r\n", Split(b64publicKey, 64));
string pemPublicKey = "-----BEGIN PUBLIC KEY-----\r\n" + b64publicKey + "\r\n-----END PUBLIC KEY-----";

(note that I had to split the string manually in blocks of 64 characters, that is the exact number given in the rfc7468, because Convert.ToBase64String() supports only the 76 line length)

xanatos
  • 109,618
  • 12
  • 197
  • 280
  • Thanks a lot. As I'm working on a cross-platform-project (signing and verifying in C#, Java, WebCrypto, PHP and NodeJs) I'm generating my keypair once in PEM encoding and use it platform wide. Your code is running on https://dotnetfiddle.net/ so I'm able to provide running solutions. – Michael Fehr Dec 28 '20 at 15:02
  • Just a note: the code is not running anymore on Windows (error message: "'ECDsa' does not contain a definition for 'ImportPkcs8PrivateKey' and no extension method 'ImportPkcs8PrivateKey' accepting a first argument of type 'ECDsa' could be found (are you missing a using directive or an assembly reference?)") but that's not important to me - more important is that the online version is running. – Michael Fehr Dec 28 '20 at 15:02
  • @MichaelFehr That method is present in .NET 5... Probably they introduced it in that version. – xanatos Dec 28 '20 at 15:27