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

Google Wallet回调签名验证的C#实现问题求助

Google Wallet回调签名验证的C#实现问题求助

大家好,我最近在做Google Wallet API的回调签名验证功能,按照官方文档的指引写了C#代码,但卡在中间签名验证这一步了——VerifyIntermediateSignature()一直返回false,怎么都过不去,想请各位大佬帮忙看看哪里出问题了!

先给大家说下背景:用Google Wallet API时可以配置回调URL,用来接收用户添加/删除卡片的通知,这些通知会带签名,需要我们验证合法性。我参考了Google Pay API的支付数据加密文档,还有Wallet的添加/删除回调文档来实现签名验证逻辑,但目前第一步的中间签名验证就失败了,下面是我完整的代码,包括可运行的测试片段,麻烦帮忙排查下问题!

我的实现代码

public bool VerifyCallbackSignature(CallbackBody? callbackBody)
{
    if (callbackBody == null) return false;

    // 构建中间签名的待签名字符串
    // 格式:length_of_sender_id || sender_id || length_of_protocol_version || protocol_version || length_of_signed_key || signed_key
    // JsonCanonicalizer.Canonicalize()用来生成规范的JSON串(我试过不用它的情况,也没生效)
    string signedKeyString = JsonCanonicalizer.Canonicalize(callbackBody.IntermediateSigningKey.SignedKey);
    string senderId = "GooglePayPasses"; // 官方指定的固定值

    byte[] signedStringForIntermediateSigningKeySignature = [
        .. GetLengthRepresentation(senderId),
        .. Encoding.UTF8.GetBytes(senderId),
        .. GetLengthRepresentation(callbackBody.ProtocolVersion),
        .. Encoding.UTF8.GetBytes(callbackBody.ProtocolVersion),
        .. GetLengthRepresentation(signedKeyString),
        .. Encoding.UTF8.GetBytes(signedKeyString),
    ];

    // 验证中间签名
    bool result = VerifyIntermediateSignature(callbackBody, signedStringForIntermediateSigningKeySignature);
    if (result == false) return false;

    // 构建消息签名的待签名字符串
    // 格式:length_of_sender_id || sender_id || length_of_recipient_id || recipient_id || length_of_protocolVersion || protocolVersion || length_of_signedMessage || signedMessage
    string signedMessageString = JsonCanonicalizer.Canonicalize(callbackBody.SignedMessage);
    byte[] signedStringForMessageSignature = [
        .. GetLengthRepresentation(senderId),
        .. Encoding.UTF8.GetBytes(senderId),
        .. GetLengthRepresentation(Constants.IssuerId),
        .. Encoding.UTF8.GetBytes(Constants.IssuerId),
        .. GetLengthRepresentation(callbackBody.ProtocolVersion),
        .. Encoding.UTF8.GetBytes(callbackBody.ProtocolVersion),
        .. GetLengthRepresentation(signedMessageString),
        .. Encoding.UTF8.GetBytes(signedMessageString)
    ];

    result = VerifyMessageSignature(callbackBody, signedStringForMessageSignature);
    if (result == false) return false;

    return true;
}

// --------------------------------------------------------------------------------------
private bool VerifyIntermediateSignature(CallbackBody callbackBody, byte[] dataBytes)
{
    foreach (GooglePublicKey googleKey in _googleKeys.Keys)
    {
        // 从Base64解析公钥
        byte[] publicKeyBytes = Convert.FromBase64String(googleKey.KeyValue);
        using ECDsa ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
        ecdsa.ImportSubjectPublicKeyInfo(publicKeyBytes, out _);

        foreach (string internalSignature in callbackBody.IntermediateSigningKey.Signatures)
        {
            // 解析回调里的签名(假设是Base64编码)
            byte[] signatureBytes = Convert.FromBase64String(internalSignature);
            if (ecdsa.VerifyData(dataBytes, signatureBytes, HashAlgorithmName.SHA256))
            {
                return true;
            }
        }
    }
    return false;
}

private bool VerifyMessageSignature(CallbackBody callbackBody, byte[] dataBytes)
{
    byte[] keyBytes = Convert.FromBase64String(callbackBody.IntermediateSigningKey.SignedKeyObject.KeyValue);
    using ECDsa ecdsa = ECDsa.Create();
    ecdsa.ImportSubjectPublicKeyInfo(keyBytes, out _);

    byte[] signatureBytes = Convert.FromBase64String(callbackBody.Signature);
    return ecdsa.VerifyData(dataBytes, signatureBytes, HashAlgorithmName.SHA256);
}

/// <summary>
/// 将字符串长度转换为4字节小端格式的字节数组
/// </summary>
/// <param name="str">目标字符串</param>
/// <returns>长度的字节表示</returns>
private byte[] GetLengthRepresentation(string str)
{
    byte[] strBytes = Encoding.UTF8.GetBytes(str);
    byte[] bytes = BitConverter.GetBytes(strBytes.Length);
    return bytes;
}

可运行的测试代码(补充片段)

public class GoogleSecurityChecker
{
    private GooglePublicKeysWrapper _googleKeys;
    public static GoogleSecurityChecker Checker { get; set; }

    public static void Main(string[] args)
    {
        GooglePublicKeysWrapper keys = new GooglePublicKeysWrapper()
        {
            Keys = [
                new GooglePublicKey() {
                    KeyValue = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEs/J7ghdwu4/b6NvqzQebS+Z80FEP8ZlGdAdWDUjfPeBKaV+YOWW0l6U/4IGXnfY9NrsND1fwxAqRq0unR/LCIg==",
                    ProtocolVersion = "ECv2SigningOnly",
                    KeyExpiration = "1749736782391"
                }
            ]
        };

        Checker = new GoogleSecurityChecker();
        Checker._googleKeys = keys;

        CallbackBody callback = new CallbackBody()
        {
            Signature = "MEUCIQDLQY9YBdOyjjAbnTgWnn/yQtsISYqnlr9WR1Leo91JvQIgXNU2XNdBaXgliN/Mwt8inDZWpUwHr6ucC7goFhOc/Ug=",
            IntermediateSigningKey = new IntermediateSigningKey()
            {
                SignedKey = "// 原代码此处内容被截断,实际测试需补充完整的SignedKey内容"
            }
        };

        // 调用验证方法
        bool isValid = Checker.VerifyCallbackSignature(callback);
        Console.WriteLine($"验证结果:{isValid}");
    }

    // 此处省略上面已贴的VerifyCallbackSignature等方法
}

// 配套模型类(假设已定义)
public class CallbackBody
{
    public string ProtocolVersion { get; set; }
    public IntermediateSigningKey IntermediateSigningKey { get; set; }
    public string SignedMessage { get; set; }
    public string Signature { get; set; }
}

public class IntermediateSigningKey
{
    public string SignedKey { get; set; }
    public List<string> Signatures { get; set; }
    public SignedKeyObject SignedKeyObject { get; set; }
}

public class SignedKeyObject
{
    public string KeyValue { get; set; }
}

public class GooglePublicKeysWrapper
{
    public List<GooglePublicKey> Keys { get; set; }
}

public class GooglePublicKey
{
    public string KeyValue { get; set; }
    public string ProtocolVersion { get; set; }
    public string KeyExpiration { get; set; }
}

public static class Constants
{
    public static string IssuerId = "你的实际IssuerId"; // 替换为自己的IssuerId
}

// 自定义JSON规范序列化工具类
public static class JsonCanonicalizer
{
    public static string Canonicalize(string json)
    {
        // 按官方要求生成规范JSON串(试过直接返回原字符串,结果也一样)
        return json;
    }
}

我试过的排查点

  1. JSON规范序列化:试过用JsonCanonicalizer对signedKey和signedMessage做规范处理,也试过直接用原字符串,结果中间签名还是验证失败;
  2. 长度表示格式:怀疑GetLengthRepresentation的字节序有问题,试过把小端改成大端,还是不行;
  3. 待签名字符串拼接:严格按照官方文档的格式拼接,senderId用的是固定值GooglePayPasses,这部分应该没出错吧?

实在找不到问题出在哪了,麻烦各位大佬帮忙看看,感激不尽!

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 07:33:10