更新用于签署AmazonAPI请求以使用Python 3的Python代码

更新用于签署AmazonAPI请求以使用Python 3的Python代码,python,python-2.7,amazon-web-services,encoding,python-3.6,Python,Python 2.7,Amazon Web Services,Encoding,Python 3.6,我有一些工作代码,可以从AmazonAPI获取一些销售数据。它在Python2.7中工作,但是我在将其更新到Python3.6时遇到了问题—错误来自于对请求进行签名。我的代码如下: import base64, hashlib, hmac, urllib from time import gmtime, strftime from requests import request import xml.etree.ElementTree as ET def get_timestamp():

我有一些工作代码,可以从AmazonAPI获取一些销售数据。它在Python2.7中工作,但是我在将其更新到Python3.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_签名方法(同样,它在python 2.7中工作)引发了该错误,它告诉我:

TypeError:key:需要字节或字节数组,但得到了“str”

在做了一些挖掘之后,我能够通过在行中添加.encode('utf-8')来纠正这一点:

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

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

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

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

没有将其作为注释添加,因为它可能超过字符限制


问题在于以下几行:

request_description = '&'.join(
    ['%s=%s' % (
        k,
        urllib.parse.quote(payload[k], safe='-_.~').encode('utf-8')
     )
     for k in sorted(payload)]
)
您正在对标头值进行编码,然后在字符串插值过程中将它们转换回
str
“%s=%s%”(…)
)。例如,对于
SignatureVersion
,这会导致

>>> str('2'.encode('ascii'))
"b'2'"
>>> '%s' % '2'.encode('ascii')
"b'2'"
这就是AWS报告的内容(使用HTML实体;
&apos;
表示


我不确定你想用这条线达到什么目的。您可以通过以下方式对有效负载进行编码:

urllib.parse.urlencode(payload).encode('ASCII')

(请参阅。)

不将此作为注释添加,因为它可能超过字符限制


问题在于以下几行:

request_description = '&'.join(
    ['%s=%s' % (
        k,
        urllib.parse.quote(payload[k], safe='-_.~').encode('utf-8')
     )
     for k in sorted(payload)]
)
您正在对标头值进行编码,然后在字符串插值过程中将它们转换回
str
“%s=%s%”(…)
)。例如,对于
SignatureVersion
,这会导致

>>> str('2'.encode('ascii'))
"b'2'"
>>> '%s' % '2'.encode('ascii')
"b'2'"
这就是AWS报告的内容(使用HTML实体;
&apos;
表示


我不确定你想用这条线达到什么目的。您可以通过以下方式对有效负载进行编码:

urllib.parse.urlencode(payload).encode('ASCII')

(参考。)

我曾经尝试过(如果你读过这个问题的话)这个问题的解决方案的可能重复-也许我用错了?我得到了要计算的签名,但它一定计算不正确,因为Amazon拒绝了它。请尝试对
SignatureVersion
使用整数。AWS的响应使用HTML实体,内容如下:
b&apos;2&apos;->b'2'
。因此,看起来在某个点上,unicode字符串
“2”
被编码,结果被转换回unicode,结果是
“b'2'”
str('2'.encode('ascii'))==“b'2'
)。编辑:为了处理任意版本号,必须使用字符串,所以这不是最终的解决方案。不确定在什么时候会发生这种转换,但它应该
解码
而不是转换为
str
。这个问题的解决方案可能是我尝试过的(如果你读了这个问题的话)-也许我应用错了?我得到了要计算的签名,但它一定计算不正确,因为Amazon拒绝了它。请尝试对
SignatureVersion
使用整数。AWS的响应使用HTML实体,内容如下:
b&apos;2&apos;->b'2'
。因此,看起来在某个点上,unicode字符串
“2”
被编码,结果被转换回unicode,结果是
“b'2'”
str('2'.encode('ascii'))==“b'2'
)。编辑:为了处理任意版本号,必须使用字符串,所以这不是最终的解决方案。不确定此转换在什么时候发生,但它应该
解码
,而不是转换为
str
。谢谢。请求描述是必要的,因为它按照AWS要求的方式对有效负载进行排序。您建议的urllib.parse.urlencode更漂亮,但排序不正确。您的评论使我意识到请求描述中的.encode('utf-8')是错误的,(因为我调用了.encode('utf-8'))在将其传递给hmac时再次出现在该行。谢谢。请求描述是必要的,因为它按照AWS要求的方式对有效负载进行排序。您建议的urllib.parse.urlencode更漂亮,但排序不正确。您的评论使我意识到请求描述中的.encode('utf-8')是错误,(因为在将它传递给hmac时,我在该行上再次调用了.encode('utf-8')。