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; } }
我试过的排查点
- JSON规范序列化:试过用
JsonCanonicalizer对signedKey和signedMessage做规范处理,也试过直接用原字符串,结果中间签名还是验证失败; - 长度表示格式:怀疑
GetLengthRepresentation的字节序有问题,试过把小端改成大端,还是不行; - 待签名字符串拼接:严格按照官方文档的格式拼接,senderId用的是固定值
GooglePayPasses,这部分应该没出错吧?
实在找不到问题出在哪了,麻烦各位大佬帮忙看看,感激不尽!
内容来源于stack exchange
相关产品推荐
相关产品推荐

