You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# ECDSA签名与指定Go验证代码兼容适配需求

问题:调整C# ECDSA签名代码适配指定Go验证逻辑

现状描述

我使用以下C#代码生成ECDSA数字签名:

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));
        }
    }
}

使用以下Go验证代码验证时,出现错误:

package main

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

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") // 待验证的原始数据

    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")
    }

    // 签名验证成功
    return nil
}

错误信息:

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

已知情况:

  • 用Go签名代码生成的签名可正常通过上述Go验证代码
  • C#生成的签名仅能适配另一版Go验证代码,但我需要不修改上述Go验证代码,仅调整C#代码,让生成的签名能通过验证。

解决方案

问题根源:C#的ECDsa.SignData默认返回IEEE P1363格式的签名(直接拼接R和S的字节数组),而目标Go验证代码期望的是ASN.1 DER格式的签名(将R和S封装成ASN.1结构)。

修改后的C#代码如下:

using System;
using System.Security.Cryptography;
using System.IO;
using System.Linq;

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;
    }

    // 将IEEE P1363格式的签名转换为ASN.1 DER格式
    private static byte[] ConvertP1363ToDer(byte[] p1363Signature, int keySizeBits)
    {
        int keySizeBytes = keySizeBits / 8;
        // 拆分R和S:P1363格式是R(keySizeBytes字节) + S(keySizeBytes字节)
        byte[] rBytes = p1363Signature.Take(keySizeBytes).ToArray();
        byte[] sBytes = p1363Signature.Skip(keySizeBytes).ToArray();

        // 去除R和S前面的零字节(ASN.1要求整数不能有前导零,除非值为0)
        rBytes = TrimLeadingZeros(rBytes);
        sBytes = TrimLeadingZeros(sBytes);

        // ASN.1 DER结构:SEQUENCE(INTEGER(R), INTEGER(S))
        using (MemoryStream ms = new MemoryStream())
        using (BinaryWriter bw = new BinaryWriter(ms))
        {
            // 写入SEQUENCE标签和长度
            bw.Write((byte)0x30); // SEQUENCE标签
            // 计算总长度:两个INTEGER的长度之和(每个INTEGER是标签(0x02)+长度+内容)
            int rLength = rBytes.Length;
            int sLength = sBytes.Length;
            int totalLength = 2 + rLength + 2 + sLength;
            if (totalLength > 0x7F)
            {
                // 长格式长度编码
                bw.Write((byte)(0x80 + (int)Math.Ceiling(totalLength / 256.0)));
                byte[] lengthBytes = BitConverter.GetBytes(totalLength);
                if (BitConverter.IsLittleEndian)
                    Array.Reverse(lengthBytes);
                bw.Write(lengthBytes.SkipWhile(b => b == 0).ToArray());
            }
            else
            {
                // 短格式长度编码
                bw.Write((byte)totalLength);
            }

            // 写入R的INTEGER结构
            bw.Write((byte)0x02); // INTEGER标签
            bw.Write((byte)rLength);
            bw.Write(rBytes);

            // 写入S的INTEGER结构
            bw.Write((byte)0x02); // INTEGER标签
            bw.Write((byte)sLength);
            bw.Write(sBytes);

            return ms.ToArray();
        }
    }

    private static byte[] TrimLeadingZeros(byte[] bytes)
    {
        int startIndex = 0;
        while (startIndex < bytes.Length && bytes[startIndex] == 0)
        {
            startIndex++;
        }
        if (startIndex == bytes.Length)
            return new byte[] { 0 }; // 如果全是零,返回单个零字节
        return bytes.Skip(startIndex).ToArray();
    }

    static void Main(string[] args)
    {
        string message = "hello world";
        byte[] data = System.Text.Encoding.UTF8.GetBytes(message);
        using (ECDsa ecdsa = ECDsa.Create())
        {   
            ecdsa.ImportPkcs8PrivateKey(Convert.FromBase64String(GetPemPrivateKey()), out _);
            // 获取P1363格式签名
            byte[] p1363Signature = ecdsa.SignData(data, HashAlgorithmName.SHA256);
            // 转换为ASN.1 DER格式(这里假设密钥是256位,根据实际密钥大小调整)
            byte[] derSignature = ConvertP1363ToDer(p1363Signature, 256);
            Console.WriteLine("Signature: " + Convert.ToBase64String(derSignature));
        }
    }
}

关键说明

  1. 格式转换逻辑:手动将C#生成的P1363格式签名拆分为R和S,再按照ASN.1 DER的SEQUENCE结构重新编码。
  2. 密钥大小适配:ConvertP1363ToDer方法的第二个参数需要和你的ECDSA密钥大小一致(比如256位、384位等),请根据实际情况调整。
  3. 前导零处理:ASN.1对整数的编码要求不能有多余的前导零,所以需要去除R和S字节数组前面的零字节(除非值本身为0)。

内容的提问来源于stack exchange,提问作者Muhammed Jishin Jamal TCP

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 17:05:56