Search code examples
c#.netgocryptographydigital-signature

c# Digital signature generation and verification


using System;
 using System.Security.Cryptography;

class Helo
{

private static string GetPemPrivateKey()
{
    string privatekey = @"-----BEGIN PRIVATE KEY-----
.....
-----END PRIVATE KEY-----";

    privatekey = privatekey.Replace("-----BEGIN PRIVATE KEY-----", "")
                           .Replace("-----END PRIVATE KEY-----", "")
                           .Replace("\n", "")
                           .Replace("\r", "");

    return privatekey;
}




    static void Main(string[] args)
    {
    

        string message = "hello world";
        byte[] data = System.Text.Encoding.UTF8.GetBytes(message);
        Console.WriteLine(  Convert.FromBase64String(GetPemPrivateKey()));
        using (ECDsa ecdsa = ECDsa.Create())
        {   


            ecdsa.ImportPkcs8PrivateKey(Convert.FromBase64String(GetPemPrivateKey()), out _);
            byte[] signature = ecdsa.SignData(data, HashAlgorithmName.SHA256);
            Console.WriteLine("Signature: " + Convert.ToBase64String(signature));
        }
        
    }
}

If I sign using the .NET code and try to verify it using the following GO CODE:

package main

import (
    "crypto/ecdsa"
    "crypto/sha256"
    "crypto/x509"
    "encoding/asn1"
    "encoding/base64"
    "encoding/pem"
    "fmt"
    "log"
    "math/big"
    "strings"
)

const ()

var publicKey = []byte(`-----BEGIN PUBLIC KEY-----
....
-----END PUBLIC KEY-----`)

type Verifier struct {
    publicKey *ecdsa.PublicKey
}

func newVerifier(publicKeyPEM []byte) (*Verifier, error) {
    block, _ := pem.Decode(publicKeyPEM)
    if block == nil || !strings.Contains(block.Type, "PUBLIC KEY") {
        return nil, fmt.Errorf("invalid public key file")
    }

    publicKey, err := x509.ParsePKIXPublicKey(block.Bytes)
    if err != nil {
        return nil, err
    }

    ecdsaPublicKey, ok := publicKey.(*ecdsa.PublicKey)
    if !ok {
        return nil, fmt.Errorf("invalid public key type")
    }
    return &Verifier{publicKey: ecdsaPublicKey}, nil
}

func main() {
    v, err := newVerifier(publicKey)
    if err != nil {
        log.Println("Error creating verifier:", err)
        return
    }
    fmt.Println("v", v.publicKey)

    signature := "SIGNATURE_HERE"
    data := []byte("your data here") // This is the data that was signed

    if err := v.VerifySignature(signature, data); err != nil {
        log.Println("Error verifying signature:", err)
    } else {
        log.Println("Signature is valid.")
    }
}

func (v *Verifier) VerifySignature(signature string, rawData []byte) error {
    sigBytes, err := base64.StdEncoding.DecodeString(signature)
    if err != nil {
        log.Printf("Error decoding base64 signature: %v", err)
        return err
    }

    hash := sha256.Sum256(rawData)

    var ecdsaSig struct {
        R, S *big.Int
    }

    if _, err := asn1.Unmarshal(sigBytes, &ecdsaSig); err != nil {
        log.Printf("Error decoding ASN.1 signature: %v", err)
        return fmt.Errorf("error decoding signature")
    }

    if !ecdsa.Verify(v.publicKey, hash[:], ecdsaSig.R, ecdsaSig.S) {
        return fmt.Errorf("signature is invalid")
    }

    // Signature verification successful
    return nil
}

it doesn't work and gives the error: tags don't match (16 vs {class:3 tag:1 length:39 isCompound:false}) {optional:false explicit:false application:false private:false defaultValue:<nil> tag:<nil> stringType:0 timeType:0 set:false omitEmpty:false} @2.

However, if I sign with SIGNER and then verify with VERIFIER, it works.

I want to make the .NET code compatible with VERIFIER. When I try to verify with VERIFIER2, this Go code works fine.

I want to use VERIFIER and do not want to change the verification code. Changes to the C# code are acceptable.


Solution

  • The C# code generates a signature in P1363 format, the Go reference code with which you want to verify requires an ASN.1/DER encoded ECDSA signature. For an explanation of both formats and their relationship, see here.

    C# supports both formats, whereby P1363 is used by default, i.e. the ASN.1/DER format must be explicitly specified with DSASignatureFormat (s. here, supported as of .NET 5):

    ...
    byte[] signature = ecdsa.SignData(data, HashAlgorithmName.SHA256, DSASignatureFormat.Rfc3279DerSequence); 
    ...
    

    With this change to the C# code, verification with the Go code works.


    BTW, the C# code can also be further simplified, as .NET also supports the import of PEM keys with ImportFromPem() (supported as of .NET 5).