VBA调用eBay API签名验证成功,Python同签名验证失败
eBay REST API 签名验证问题:VBA成功但Python失败
问题背景
使用VBA代码通过数字签名调用eBay的REST API获取卖家资金汇总时,无论是否启用签名都能正常返回结果。但用Python实现相同逻辑(已确认签名完全一致)时,启用签名就会返回签名验证失败,禁用签名则响应正常。
VBA工作代码(关键部分)
Set http = New MSXML2.XMLHTTP60 created_time = DateDiff("s", "1/1/1970", now()) dig_sig = TRUE url = "https://apiz.ebay.com/sell/finances/v1/seller_funds_summary" With http .Open "GET", url .setRequestHeader "Content-Type", "application/json" .setRequestHeader "Accept", "application/json" .setRequestHeader "Authorization", "Bearer " & USER_TOKEN If dig_sig Then signature_input = "sig1=(""x-ebay-signature-key"" ""@method"" ""@path"" ""@authority"");created=" & created_time signature = getSignature("GET", _ "/sell/finances/v1/seller_funds_summary", _ "apiz.ebay.com", _ created_time) .setRequestHeader "signature-input", signature_input .setRequestHeader "signature", "sig1=:" & signature & ":" .setRequestHeader "x-ebay-signature-key", JWE .setRequestHeader "x-ebay-enforce-signature", "true" End If .Send Do Until .readyState = 4 DoEvents Application.Wait (Now + 0.00005) Loop Debug.Print .responseText End With
Python代码(签名验证失败)
import time, requests dig_sig = True request_url = "https://apiz.ebay.com/sell/finances/v1/seller_funds_summary" if dig_sig: created_time = int(time.time()) sig_input = f'sig1=("x-ebay-signature-key" "@method" "@path" "@authority");created={created_time}' signature = getSignature("GET", "/sell/finances/v1/seller_funds_summary", "apiz.ebay.com", created_time) public_key_jwe = *** jwe supplied by eBay *** headers = { "Content-Type": "application/json", "Accept": "application/json", "Authorization": "Bearer " + getUserToken(), "Signature-Input": sig_input, "Signature": f'sig1=:{signature}:', "x-ebay-signature-key": public_key_jwe, "x-ebay-enforce-signature": "true" } else: headers = { "Authorization" : "Bearer " + token, "Accept" : "application/json", "Content-Type" : "application/json" } try: response = requests.get(request_url, headers=headers) print(response.text) except Exception as e: print(e)
响应情况
- 当Python代码中
dig_sig=True时,返回错误:
{ "errors": [ { "errorId": 215122, "domain": "ACCESS", "category": "REQUEST", "message": "Signature validation failed", "longMessage": "Signature validation failed to fulfill the request." } ] }
- 当
dig_sig=False时,返回正常结果:
{"totalFunds":{"value":"1.00","currency":"GBP"},"processingFunds":{"value":"0.00","currency":"GBP"},"availableFunds":{"value":"1.00","currency":"GBP"}}
环境信息
两段代码运行于同一台电脑,软件版本:
- Microsoft Excel for Microsoft 365 MSO (版本2210 Build 16.0.15726.20188) 64位
- Microsoft Visual Basic for Applications 7.1
- Python 3.7.6
补充:Python签名函数实现
使用PyCryptodome的版本
需先安装依赖:
pip install pycryptodome
签名函数代码:
import base64 from Crypto.PublicKey import ECC from Crypto.Signature import eddsa def getSignature(private_key, public_key_jwe, method, path, authority, created_time): try: build_key = f"-----BEGIN PRIVATE KEY-----\n{private_key}\n-----END PRIVATE KEY-----" key = ECC.import_key(build_key) signer = eddsa.new(key, mode='rfc8032') sigbase = (f'"x-ebay-signature-key": {public_key_jwe}\n' + f'"@method": {method}\n' + f'"@path": {path}\n' + f'"@authority": {authority}\n' + f'"@signature-params": ("x-ebay-signature-key" "@method" "@path" "@authority");created={created_time}' ) signature = signer.sign(sigbase.encode()) return base64.b64encode(signature).decode() except Exception as e: print(e) return False
基于RFC8032的原生实现
调用sign(secret, msg)函数并传入sign(private_key, sigbase),也能生成相同签名:
import hashlib def sha512(s): return hashlib.sha512(s).digest() # Base field Z_p p = 2**255 - 19 def modp_inv(x): return pow(x, p-2, p) # Curve constant d = -121665 * modp_inv(121666) % p # Group order q = 2**252 + 27742317777372353535851937790883648493 def sha512_modq(s): return int.from_bytes(sha512(s), "little") % q ## 点运算相关函数 # 点用扩展坐标(X, Y, Z, T)表示,x = X/Z, y = Y/Z, x*y = T/Z def point_add(P, Q): A, B = (P[1]-P[0]) * (Q[1]-Q[0]) % p, (P[1]+P[0]) * (Q[1]+Q[0]) % p; C, D = 2 * P[3] * Q[3] * d % p, 2 * P[2] * Q[2] % p; E, F, G, H = B-A, D-C, D+C, B+A; return (E*F, G*H, F*G, E*H); # 计算Q = s * Q def point_mul(s, P): Q = (0, 1, 1, 0) # 单位元 while s > 0: if s & 1: Q = point_add(Q, P) P = point_add(P, P) s >>= 1 return Q def point_equal(P, Q): # x1/z1 == x2/z2 等价于 x1*z2 == x2*z1 if (P[0] * Q[2] - Q[0] * P[2]) % p != 0: return False if (P[1] * Q[2] - Q[1] * P[2]) % p != 0: return False return True ## 点压缩相关函数 # -1的平方根 modp_sqrt_m1 = pow(2, (p-1) // 4, p) # 根据y坐标和符号恢复x坐标,失败则返回None def recover_x(y, sign): if y >= p: return None x2 = (y*y-1) * modp_inv(d*y*y+1) if x2 == 0: if sign: return None else: return 0 # 计算x2的平方根 x = pow(x2, (p+3) // 8, p) if (x*x - x2) % p != 0: x = x * modp_sqrt_m1 % p if (x*x - x2) % p != 0: return None if (x & 1) != sign: x = p - x return x # 基点 g_y = 4 * modp_inv(5) % p g_x = recover_x(g_y, 0) G = (g_x, g_y, 1, g_x * g_y % p) def point_compress(P): zinv = modp_inv(P[2]) x = P[0] * zinv % p y = P[1] * zinv % p return int.to_bytes(y | ((x & 1) << 255), 32, "little") def point_decompress(s): if len(s) != 32: raise Exception("Invalid input length for decompression") y = int.from_bytes(s, "little") sign = y >> 255 y &= (1 << 255) - 1 x = recover_x(y, sign) if x is None: return None else: return (x, y, 1, x*y % p) ## 私钥处理函数 def secret_expand(secret): if len(secret) != 32: raise Exception("Bad size of private key") h = sha512(secret) a = int.from_bytes(h[:32], "little") a &= (1 << 254) - 8 a |= (1 << 254) return (a, h[32:]) def secret_to_public(secret): (a, dummy) = secret_expand(secret) return point_compress(point_mul(a, G)) ## 签名函数 def sign(secret, msg): a, prefix = secret_expand(secret) A = point_compress(point_mul(a, G)) r = sha512_modq(prefix + msg) R = point_mul(r, G) Rs = point_compress(R) h = sha512_modq(Rs + A + msg) s = (r + h * a) % q return Rs + int.to_bytes(s, 32, "little") ## 验证函数 def verify(public, msg, signature): if len(public) != 32: raise Exception("Bad public key length") if len(signature) != 64: Exception("Bad signature length") A = point_decompress(public) if not A: return False Rs = signature[:32] R = point_decompress(Rs) if not R: return False s = int.from_bytes(signature[32:], "little") if s >= q: return False h = sha512_modq(Rs + public + msg) sB = point_mul(s, G) hA = point_mul(h, A) return point_equal(sB, point_add(R, hA))
注:由于无法直接从PEM文件提取32字节私钥,最终选择使用PyCryptodome库实现,代码更简洁易用。
内容的提问来源于stack exchange,提问作者ecom-tech
相关产品推荐
相关产品推荐

