python-3.x - API VBA 的 eBay 数字签名可以,但 Python 签名验证无法满足请求

标签 python-3.x digital-signature ebay-api vba7

我可以使用以下 VBA 代码成功地从 eBay 的 REST API 获得带有数字签名的响应(我没有包含所有代码,因为代码相当多,但下面是重要部分):

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

这将成功返回卖家资金摘要,并将 dig_sig 设置为 TRUE 或 FALSE。

现在,使用具有相同 header 和签名的 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)

当 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"}}

我尝试对 VBA 和 Python 代码使用相同的签名,并期望得到相同的 OK 响应。但是,Python 代码不接受签名。 VBA 和 Python 代码都在同一台 PC 上运行。软件版本:

适用于 Microsoft 365 MSO 的 Microsoft Excel(版本 2210 内部版本 16.0.15726.20188)64 位

微软 Visual Basic 应用程序 7.1

Python 3.7.6


Xrender 更新:

下面是一个稍微修改过的 getSignature() 函数,它接受私钥和公钥作为输入参数。我不确定它是否会对您有帮助,因为它使用 Python 库。

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

为了让它工作,我必须先安装 pycryptodome:

pip install pycryptodome

如果您将 sign(secret, msg) 函数作为 sign(private_key, sigbase) 调用,以下 Python 代码也会生成相同的结果签名

来源:https://www.rfc-editor.org/rfc/rfc8032#section-6

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

## Then follows functions to perform point operations.

# Points are represented as tuples (X, Y, Z, T) of extended
# coordinates, with 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);


# Computes Q = s * Q
def point_mul(s, P):
    Q = (0, 1, 1, 0)  # Neutral element
    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

## Now follows functions for point compression.

# Square root of -1
modp_sqrt_m1 = pow(2, (p-1) // 4, p)

# Compute corresponding x-coordinate, with low bit corresponding to
# sign, or return None on failure
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

    # Compute square root of 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


# Base point
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)

## These are functions for manipulating the private key.

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))


## The signature function works as below.

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")

## And finally the verification function.

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))

但是,由于我无法弄清楚如何仅使用 Python 从 PEM 文件中的私钥获取 32 字节私钥,因此我选择使用 Crypto 库,因为它的代码更简单、更短。

最佳答案

天哪!我发现了我的错误!

"x-ebay-signature-key" "@method" "@path "@authority"

在“@path”之后缺少双引号 替换为:

"x-ebay-signature-key" "@method" "@path" "@authority"

现在它正在工作。无论如何,我希望本主题可以帮助其他尝试让 API 的 eBay 数字签名正常工作的人。

关于python-3.x - API VBA 的 eBay 数字签名可以,但 Python 签名验证无法满足请求,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74530218/

相关文章:

python-3.x - 使用 python requests 模块登录网站

python - 位置数字总和(两个数字根据其位置的数字之和)

c# - CMS 签名 - 时间戳和计数器签名有什么区别

macos - 漏洞?在 codesign --remove-signature 功能中

windows - 如何对 Inno Setup 生成的卸载文件进行数字签名?

ebay-api - eBay 每日优惠 API

python - eBay SOAP API - 缺少 SOA 操作名称 header

Python JSON 添加键值对

python - “jupyter notebook”命令在 Linux 上不起作用

python - 如何更改连续 webAPI 调用的一个 XML 标记的内容?