Ebay 公钥验证在 C# 中失败,但在节点中工作

Posted

技术标签:

【中文标题】Ebay 公钥验证在 C# 中失败,但在节点中工作【英文标题】:Ebay public key verification in fails in C# but works in node 【发布时间】:2021-07-13 16:22:18 【问题描述】:

我一直在尝试在 C# .net core 3.1 中验证 ebay 通知,但它拒绝签名。

我在 node.js 中有一个工作的最小示例。请注意,我必须编辑正文内容。签名验证OK。

const crypto = require('crypto');

const validateSignature = (message, signature, publicKey) => 

    const verifier = crypto.createVerify('ssl3-sha1');

    verifier.update(JSON.stringify(message));

    return verifier.verify(`-----BEGIN PUBLIC KEY-----\n$publicKey\n-----END PUBLIC KEY-----`, signature, "base64");
;

const BODY = "redacted";
const SIG = `MEUCIG9ANqAZ3MIS6CDNcJFf/5d8VlIyHuHZlgw57HLzALl+AiEA0Egsw0y7VLvsUxKhBhivVr/Ee6O69lmkIchftQ2Fnqs=`;
const KEY = `MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZhhxXKtR+TOvtDbgTPCkSof02qgBB7IsYOyf76ilExJ/upAa/vKIKheOoCyOpcLmi4t0b4uepb7LLjmMr90FUg==`;

try 
    const result = validateSignature(BODY, SIG, KEY);
   
    console.log("verification: ", result);   
 catch(Err) 
    console.error(Err);



这返回真。

这是同样的例子,在 .net core 3.1 中,内容再次被编辑

class Program

    public static bool ValidateEcdsa(string bodyData, string signature, string keyString)
    
        try
        
            using (var ecdsa = ECDsa.Create())
            
                var contentBytes = Encoding.UTF8.GetBytes(bodyData);

                var signatureBytes = Convert.FromBase64String(signature ?? "");

                var keyBytes = Convert.FromBase64String(keyString);

                ecdsa.ImportSubjectPublicKeyInfo(keyBytes, out _);

                bool success = ecdsa.VerifyData(contentBytes, signatureBytes, HashAlgorithmName.SHA1);

                return success;
            
        
        catch (Exception ex)
        
            Console.Error.WriteLine(ex.Message);
        
        return false;
    

    public static string BODY = "redacted";
    public static string SIG = "MEUCIG9ANqAZ3MIS6CDNcJFf/5d8VlIyHuHZlgw57HLzALl+AiEA0Egsw0y7VLvsUxKhBhivVr/Ee6O69lmkIchftQ2Fnqs=";
    public static string KEY = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZhhxXKtR+TOvtDbgTPCkSof02qgBB7IsYOyf76ilExJ/upAa/vKIKheOoCyOpcLmi4t0b4uepb7LLjmMr90FUg==";

    static void Main(string[] args)
    
        var result = ValidateEcdsa(BODY, SIG, KEY);
        Console.WriteLine($"verification: result");
    

这将毫无例外地加载公钥信息,但返回 false。

.net 示例和节点示例有什么不同?

关于开始和结束标题的注释

节点库需要---begin------end--- 标头。没有这个它会失败。 如果您有这些标头,.net 库将不会导入密钥,因此在此最小示例中已将其删除。

身体是一样的吗?

我无法展示原始尸体,因此必须在此处对其进行编辑。但是,我使用 sha1 哈希来确保它们在两个最小示例中是完全相同的字符串。

节点:

var shasum = crypto.createHash('sha1')
shasum.update(BODY)
console.log("hash of body data", shasum.digest('hex'))

.net:

using (SHA1Managed sha1 = new SHA1Managed())

    var hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(input));
    var sb = new StringBuilder(hash.Length * 2);

    foreach (byte b in hash)
    
        sb.Append(b.ToString("x2"));
    

    return sb.ToString();

这两个都给出了相同的哈希序列。

【问题讨论】:

如果您对 ---BEGIN--- 和 ---END--- 标头之间的位进行 base64 解码,您应该得到一个有效 ASN.1 的字节数组。 肯定是这样,否则它将被关闭而不是被否决。您有一个 base64 编码的密钥。您正在尝试以 utf8 格式获取其字节(为什么是所有可能的 utf8?)并将其传递给 ImportRSAPublicKey,即使它是 specifically says "如果该值是 Base64 编码或 PEM 文本格式,调用者必须在调用此方法之前对内容进行 Base64 解码”。那将是缺乏研究。 如何将密钥传递给函数?看来您需要从 BEGIN 和 END 标记之间提取它并解码 base64。 API 在这里。您可以从示例中看到它返回的内容。它看起来确实太小了,但我不知道它到底包含什么。 developer.ebay.com/api-docs/commerce/notification/resources/… 这不是 RSA 密钥,而是 ECDSA 密钥——“算法”值。 【参考方案1】:

.net ECDsa 库默认为 IEEE P1363 但 ebay 使用 RFC第3279章

你需要 .net core 5.0。

然后您可以简单地将可选参数传递给 VerifyData 方法。

bool success = ecdsa.VerifyData(contentBytes, signatureBytes, HashAlgorithmName.SHA1, DSASignatureFormat.Rfc3279DerSequence);

节点版本自动选择 RFC 3279。

您也可以使用 BouncyCastle

using System;
using System.IO;
using System.Text;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;


static bool VerifyData(string bodyData, string signature, string keyString)

    var pubkeyString = $"-----BEGIN PUBLIC KEY-----\nkeyString\n-----END PUBLIC KEY-----";
    var data = bodyData;
    var signatureBytes = Convert.FromBase64String(signature);

    var pemreader = new PemReader(new StringReader(pubkeyString));
    var pubkey = (AsymmetricKeyParameter)pemreader.ReadObject();

    // Verify using the public key
    var signer = SignerUtilities.GetSigner("SHA1withECDSA");
    signer.Init(false, pubkey);
    signer.BlockUpdate(Encoding.UTF8.GetBytes(data), 0, data.Length);
    var success = signer.VerifySignature(signatureBytes);

    return success;

这支持 .net 标准 2.0,并且与大多数版本的 .net 框架和 .net 核心兼容。

【讨论】:

对于低于 .NET 5 的版本,从 ASN.1/DER 到 r|s(或从 RFC 3279 格式到 IEEE P1363)的转换也可以自己完成/实现。不是很复杂,详细描述here。 @user9014097 我不确定如果我们得到第三方的签名和公钥,这是否会起作用。但更简单的解决方案是使用更好的库 ASN.1/DER 格式的签名始终可以转换为 r|s 格式(反之亦然)。但当然,使用支持两种格式或转换的库(例如 BouncyCastle)会更容易。 @user9014097 你能用一些代码发布一个答案来执行转换吗? rs 包含在 ASN.1 签名中。 Here可以找到ASN.1格式的简单描述,从中可以看出rs的位置和长度。提取rs(并删除可能的符号字节)后,只需将它们连接起来:r|s。如果我找到时间并且没有人打败我,我会发布一个实现。

以上是关于Ebay 公钥验证在 C# 中失败,但在节点中工作的主要内容,如果未能解决你的问题,请参考以下文章

强命名程序集验证在本地工作,但在测试设备上失败

在 C# 中使用 RS256(非对称)验证 JWT

在 C# 中连接到 Websphere MQ 有效,但在 C++ 中失败,代码为 2058 (MQRC_Q_MGR_NAME_ERROR)

连接gitee仓库失败代理节点被占用解决办法

如何验证失败的 ElasticSearch 恢复?

c# .net4.0验证视图状态 MAC 失败!试过网上的webconfig 节点添加配置,在网页page添加属性均无效。