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)); } } }
关键说明
- 格式转换逻辑:手动将C#生成的P1363格式签名拆分为R和S,再按照ASN.1 DER的SEQUENCE结构重新编码。
- 密钥大小适配:
ConvertP1363ToDer方法的第二个参数需要和你的ECDSA密钥大小一致(比如256位、384位等),请根据实际情况调整。 - 前导零处理:ASN.1对整数的编码要求不能有多余的前导零,所以需要去除R和S字节数组前面的零字节(除非值本身为0)。
内容的提问来源于stack exchange,提问作者Muhammed Jishin Jamal TCP
相关产品推荐
相关产品推荐

