Python 3.5 中的 Hmac 散列错误

Hmac Hashing Error in Python 3.5

我正在尝试使用 hmac 散列 API 秘密。但是我无法使用 Python 3.5.

让它工作

问题代码如下:

sign = hmac.new(self.Secret, post_data, hashlib.sha512).hexdigest()

这是错误:

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

我试过像这样先编码...

secret = b'api_secret_here'

也尝试过...

sign = hmac.new(self.Secret.encode('utf-8'), post_data, hashlib.sha512).hexdigest()

和...

sign = hmac.new(self.Secret.encode(), post_data, hashlib.sha512).hexdigest()


全部报错:

TypeError: Unicode-objects must be encoded before hashing

完整上下文中的代码如下:

import hashlib
import hmac
import json
import sys
import time
from datetime import datetime

# Tested on Python 2.7.6 & 3.4.3
if sys.version_info[0] == 3:
    from urllib.request import Request, urlopen
    from urllib.parse import urlencode
else:
    from urllib2 import Request, urlopen
    from urllib import urlencode

minute = 60
hour = minute * 60
day = hour * 24
week = day * 7
month = day * 30
year = day * 365

# Possible Commands
PUBLIC_COMMANDS = ['returnTicker', 'return24hVolume', 'returnOrderBook', 'returnTradeHistory', 'returnChartData',
                   'returnCurrencies', 'returnLoanOrders']
PRIVATE_COMMANDS = ['returnBalances', 'returnCompleteBalances', 'returnDepositAddresses', 'generateNewAddress',
                    'returnDepositsWithdrawals', 'returnOpenOrders', 'returnTradeHistory',
                    'returnAvailableAccountBalances', 'returnTradableBalances', 'returnOpenLoanOffers',
                    'returnActiveLoans', 'createLoanOffer', 'cancelLoanOffer', 'toggleAutoRenew', 'buy', 'sell',
                    'cancelOrder', 'moveOrder', 'withdraw', 'transferBalance', 'returnMarginAccountSummary',
                    'marginBuy', 'marginSell', 'getMarginPosition', 'closeMarginPosition']


class Poloniex:
    def __init__(self, APIKey='', Secret=''):
        self.APIKey = APIKey
        self.Secret = Secret
        # Conversions
        self.timestamp_str = lambda timestamp=time.time(), format="%Y-%m-%d %H:%M:%S": datetime.fromtimestamp(
            timestamp).strftime(format)
        self.str_timestamp = lambda datestr=self.timestamp_str(), format="%Y-%m-%d %H:%M:%S": int(
            time.mktime(time.strptime(datestr, format)))
        self.float_roundPercent = lambda floatN, decimalP=2: str(round(float(floatN) * 100, decimalP)) + "%"

        # PUBLIC COMMANDS
        self.marketTicker = lambda x=0: self.api('returnTicker')
        self.marketVolume = lambda x=0: self.api('return24hVolume')
        self.marketStatus = lambda x=0: self.api('returnCurrencies')
        self.marketLoans = lambda coin: self.api('returnLoanOrders', {'currency': coin})
        self.marketOrders = lambda pair='all': self.api('returnOrderBook', {'currencyPair': pair})
        self.marketChart = lambda pair, period=day, start=time.time() - (month * 2), end=time.time(): self.api(
            'returnChartData', {'currencyPair': pair, 'period': period, 'start': start, 'end': end})
        self.marketTradeHist = lambda pair: self.api('returnTradeHistory',
                                                     {'currencyPair': pair})  # NEEDS TO BE FIXED ON Poloniex

        # PRIVATE COMMANDS
        # self.myTradeHist = lambda pair: self.api('returnTradeHistory',{'currencyPair':pair})
        self.myAvailBalances = lambda x=0: self.api('returnAvailableAccountBalances')
        self.myMarginAccountSummary = lambda x=0: self.api('returnMarginAccountSummary')
        self.myMarginPosition = lambda pair='all': self.api('getMarginPosition', {'currencyPair': pair})
        self.myCompleteBalances = lambda x=0: self.api('returnCompleteBalances')
        self.myAddresses = lambda x=0: self.api('returnDepositAddresses')
        self.myOrders = lambda pair='all': self.api('returnOpenOrders', {'currencyPair': pair})
        self.myDepositsWithdraws = lambda x=0: self.api('returnDepositsWithdrawals')
        self.myTradeableBalances = lambda x=0: self.api('returnTradableBalances')
        self.myActiveLoans = lambda x=0: self.api('returnActiveLoans')
        self.myOpenLoanOrders = lambda x=0: self.api('returnOpenLoanOffers')
        ## Trading functions ##
        self.createLoanOrder = lambda coin, amount, rate: self.api('createLoanOffer',
                                                                   {'currency': coin, 'amount': amount, 'duration': 2,
                                                                    'autoRenew': 0, 'lendingRate': rate})
        self.cancelLoanOrder = lambda orderId: self.api('cancelLoanOffer', {'orderNumber': orderId})
        self.toggleAutoRenew = lambda orderId: self.api('toggleAutoRenew', {'orderNumber': orderId})
        self.closeMarginPosition = lambda pair: self.api('closeMarginPosition', {'currencyPair': pair})
        self.marginBuy = lambda pair, rate, amount, lendingRate=2: self.api('marginBuy',
                                                                            {'currencyPair': pair, 'rate': rate,
                                                                             'amount': amount,
                                                                             'lendingRate': lendingRate})
        self.marginSell = lambda pair, rate, amount, lendingRate=2: self.api('marginSell',
                                                                             {'currencyPair': pair, 'rate': rate,
                                                                              'amount': amount,
                                                                              'lendingRate': lendingRate})
        self.buy = lambda pair, rate, amount: self.api('buy', {'currencyPair': pair, 'rate': rate, 'amount': amount})
        self.sell = lambda pair, rate, amount: self.api('sell', {'currencyPair': pair, 'rate': rate, 'amount': amount})
        self.cancelOrder = lambda orderId: self.api('cancelOrder', {'orderNumber': orderId})
        self.moveOrder = lambda orderId, rate, amount: self.api('moveOrder', {'orderNumber': orderId, 'rate': rate,
                                                                              'amount': amount})
        self.withdraw = lambda coin, amount, address: self.api('withdraw',
                                                               {'currency': coin, 'amount': amount, 'address': address})
        self.transferBalance = lambda coin, amount, fromac, toac: self.api('transferBalance',
                                                                           {'currency': coin, 'amount': amount,
                                                                            'fromAccount': fromac, 'toAccount': toac})

    #####################
    # Main Api Function #
    #####################
    def api(self, command, args={}):
        """
        returns 'False' if invalid command or if no APIKey or Secret is specified (if command is "private")
        returns {"error":"<error message>"} if API error
        """
        args['command'] = command
        if command in PRIVATE_COMMANDS:
            if len(self.APIKey) < 2 or len(self.Secret) < 2:
                print("An APIKey and Secret is needed!")
                return False
            url, args['nonce'] = ['https://poloniex.com/tradingApi', int(time.time() * 42)]
            post_data = urlencode(args)
            sign = hmac.new(self.Secret, post_data, hashlib.sha512).hexdigest()
            headers = {'Sign': sign, 'Key': self.APIKey}
            ret = urlopen(Request(url, post_data, headers))
            return json.loads(ret.read().decode(encoding='UTF-8'))
        elif command in PUBLIC_COMMANDS:
            url = 'https://poloniex.com/public?'
            if not args:
                ret = urlopen(Request(url + command))
                return json.loads(ret.read().decode(encoding='UTF-8'))
            else:
                ret = urlopen(Request(url + urlencode(args)))
                return json.loads(ret.read().decode(encoding='UTF-8'))
        else:
            return False

hmac.new()keymsg 参数都必须是 bytes 对象。您正在为它提供一个 str 对象,因为这是 urllib.parse.urlencode() function 产生的:

Convert [...] to a percent-encoded ASCII text string. If the resultant string is to be used as a data for POST operation with the urlopen() function, then it should be encoded to bytes, otherwise it would result in a TypeError.

因此,您的 self.Secret 不仅应该是字节,还需要对 post_data 元素进行编码,更重要的是,您将把它传递给 urlopen() 以获得 POST 操作:

post_data = urlencode(args).encode('ASCII')
sign = hmac.new(self.Secret, post_data, hashlib.sha512).hexdigest()
headers = {'Sign': sign, 'Key': self.APIKey}
ret = urlopen(Request(url, post_data, headers))