更新 Python 代码以签署 Amazon API 请求以使用 Python 3

Updating Python code for signing an Amazon API request to work with Python 3

我有一些工作代码可以从 Amazon API 获取一些销售数据。它在 python 2.7 中工作,但我无法将其更新到 python 3.6 错误来自签署请求。我的代码如下:

import base64, hashlib, hmac, urllib
from time import gmtime, strftime
from requests import request
import xml.etree.ElementTree as ET

def get_timestamp():
    """Return correctly formatted timestamp"""
    return strftime("%Y-%m-%dT%H:%M:%SZ", gmtime())

def calc_signature(method, domain, URI, request_description, key):
    """Calculate signature to send with request"""
    sig_data = method + '\n' + \
        domain.lower() + '\n' + \
        URI + '\n' + \
        request_description

    hmac_obj = hmac.new(key, sig_data, hashlib.sha256)
    digest = hmac_obj.digest()

    return  urllib.parse.quote(base64.b64encode(digest), safe='-_+=/.~')

SECRET_KEY = 'xxxxxxxxxxxxxxxxxxxxx'
AWS_ACCESS_KEY = 'xxxxxxxxxxxxxxxxxxx'
SELLER_ID = 'xxxxxxxxxxxxxxxxxx'
MARKETPLACE_ID = marketplace_id
version = '2013-09-01'

Action = 'ListOrders'
SignatureMethod = 'HmacSHA256'
SignatureVersion = '2'
Timestamp = get_timestamp()
Version = '2013-09-01'
CreatedAfter = '2017-05-26T23:00:57Z'
URI = '/Orders/2013-09-01'
domain = 'mws.amazonservices.com'
proto = 'https://'
method = 'POST'

payload = {
    'AWSAccessKeyId': AWS_ACCESS_KEY,
    'Action': Action,
    'SellerId': SELLER_ID,
    'SignatureVersion': SignatureVersion,
    'Timestamp': Timestamp,
    'Version': Version,
    'SignatureMethod': SignatureMethod,
    'CreatedAfter': CreatedAfter,
    'MarketplaceId.Id.1': MARKETPLACE_ID
}

request_description = '&'.join(['%s=%s' % (k, urllib.parse.quote(payload[k], safe='-_.~').encode('utf-8')) for k in sorted(payload)])

sig = calc_signature(method, domain, URI, request_description, SECRET_KEY)

url = '%s%s?%s&Signature=%s' % \
    (proto+domain, URI, request_description, urllib.parse.quote(sig))

headers = {
    'Host': domain,
    'Content-Type': 'text/xml',
    'x-amazon-user-agent': 'python-requests/1.2.0 (Language=Python)'
}

r = request(method, url, headers=headers)

print(r.status_code)

print(r.text)

错误是由 calc_signature 方法抛出的(同样,这在 python 2.7 中有效)告诉我:

TypeError: key: expected bytes or bytearray, but got 'str'

在做了一些挖掘之后,我能够通过将 .encode('utf-8') 添加到行来更正该问题:

hmac_obj = hmac.new(key.encode('utf-8'), sig_data.encode('utf-8'), hashlib.sha256)

在将编码应用于 hmac.new 的输入后,代码现在可以运行,但是,亚马逊拒绝了请求并说:

<Error>
    <Type>Sender</Type>
    <Code>InvalidParameterValue</Code>
    <Message>Value b&apos;2&apos; for parameter SignatureVersion is invalid.
    </Message>
</Error>

我无法找出 hmac 模块在 python 版本之间发生了什么变化,导致它计算出不正确的签名。

不将其添加为评论,因为它可能超出字符数限制。


问题出在下面一行:

request_description = '&'.join(
    ['%s=%s' % (
        k,
        urllib.parse.quote(payload[k], safe='-_.~').encode('utf-8')
     )
     for k in sorted(payload)]
)

您正在对 header 值进行编码,然后在字符串插值 ('%s=%s' % (...)) 期间将它们转换回 str。例如,对于 SignatureVersion,这会导致

>>> str('2'.encode('ascii'))
"b'2'"
>>> '%s' % '2'.encode('ascii')
"b'2'"

这是 AWS 报告的内容(使用 HTML 个实体;&apos; 表示 ')。


我不确定你想用这条线达到什么目的。您可以通过

对有效载荷进行编码
urllib.parse.urlencode(payload).encode('ASCII')

(参考this answer。)