Go和Python的HMAC库给出了不同的结果。

huangapple go评论90阅读模式
英文:

Go and Python HMAC libraries give different results

问题

我正在尝试使用HMAC/sha512 API密钥方案进行身份验证。

以下是一个正常工作的Python示例代码:

import urllib, urllib2
import json
import time
import hmac, hashlib
import sys
api_key = "J88PJQEG-LKKICZLN-3H33GWIB-97OGW8I5"
secret = "b9f2e97c5c43e8e759c06219b37fce78478985ae4b0176d47182419c434567405a9386a854bca5d068135d1163d3f1cc9b877cd5d95d03c9d100be6ffcaac864"

def auth_request(command, args):
    args = [("command", command), ("nonce", 3000000000)]
    post_data = urllib.urlencode(args)
    print post_data
    sign = hmac.new(secret, post_data, hashlib.sha512).hexdigest()
    print sign
    headers = {
        'Sign': sign,
        'Key': api_key
    }

    ret = urllib2.urlopen(urllib2.Request('https://poloniex.com/tradingApi', post_data, headers))
    return ret.read()

print auth_request("returnBalances", {})

现在是我的Go实现:

const (
    public_api_url  = "https://poloniex.com/public"
    private_api_url = "https://poloniex.com/tradingApi"
    pubkey := "J88PJQEG-LKKICZLN-3H33GWIB-97OGW8I5"
    privkey := "b9f2e97c5c43e8e759c06219b37fce78478985ae4b0176d47182419c434567405a9386a854bca5d068135d1163d3f1cc9b877cd5d95d03c9d100be6ffcaac864"
)
func CallPrivate(method string, args map[string]string) dynjson.DynNode {

    if args == nil {
        args = make(map[string]string)
    }
    v := make(url.Values)
    v.Set("nonce", "3000000000") //strconv.Itoa(int((time.Now().Unix()))*1000))
    v.Set("command", method)
    for k, val := range args {
        v.Set(k, val)
    }
    final_url := private_api_url + "?" + v.Encode()
    log.Println(final_url)
    client := &http.Client{}
    post_data := v.Encode()
    secret_bytes, err := hex.DecodeString(privkey)
    check(err)
    sighash := hmac.New(sha512.New, secret_bytes)
    sighash.Write([]byte(post_data))
    sigstr := hex.EncodeToString(sighash.Sum(nil))
    log.Println(sigstr)
    j, err := json.Marshal(args)
    check(err)
    buf := bytes.NewBuffer(j)
    req, err := http.NewRequest("POST", final_url, buf)
    check(err)
    req.Header.Set("Key", pubkey)
    req.Header.Set("Sign", sigstr)
    res, err := client.Do(req)
    check(err)
    defer res.Body.Close()
    if res.StatusCode != 200 {
        log.Println("bad status code")
        log.Printf("%s", res)
        panic(errors.New(res.Status))
    }
    res_body, err := ioutil.ReadAll(res.Body)
    check(err)
    //log.Printf("%v", res_body)
    return dynjson.NewFromBytes(res_body)

}

CallPrivate("returnBalances", nil)

我目前卡在调用Python实现上,这不是一个很好的解决方案。

英文:

I am trying to authenticate using HMAC/sha512 API key scheme.

Here is the example Python code, which works fine:

import urllib, urllib2
import json
import time
import hmac, hashlib
import sys
api_key = "J88PJQEG-LKKICZLN-3H33GWIB-97OGW8I5"
secret = "b9f2e97c5c43e8e759c06219b37fce78478985ae4b0176d47182419c434567405a9386a854bca5d068135d1163d3f1cc9b877cd5d95d03c9d100be6ffcaac864"
# cmd = sys.argv[1]
# args = json.loads(sys.argv[2])
def auth_request(command, args):
args = [("command", command), ("nonce", 3000000000)]
post_data = urllib.urlencode(args)
print post_data
sign = hmac.new(secret, post_data, hashlib.sha512).hexdigest()
print sign
headers = {
'Sign': sign,
'Key': api_key
}
ret = urllib2.urlopen(urllib2.Request('https://poloniex.com/tradingApi', post_data, headers))
return ret.read()
print auth_request("returnBalances", {})

And now my Go implementation:

const (
public_api_url  = "https://poloniex.com/public"
private_api_url = "https://poloniex.com/tradingApi"
pubkey := "J88PJQEG-LKKICZLN-3H33GWIB-97OGW8I5"
privkey := "b9f2e97c5c43e8e759c06219b37fce78478985ae4b0176d47182419c434567405a9386a854bca5d068135d1163d3f1cc9b877cd5d95d03c9d100be6ffcaac864"
)
func CallPrivate(method string, args map[string]string) dynjson.DynNode {
if args == nil {
args = make(map[string]string)
}
v := make(url.Values)
v.Set("nonce", "3000000000") //strconv.Itoa(int((time.Now().Unix()))*1000))
v.Set("command", method)
for k, val := range args {
v.Set(k, val)
}
final_url := private_api_url + "?" + v.Encode()
log.Println(final_url)
client := &http.Client{}
post_data := v.Encode()
secret_bytes, err := hex.DecodeString(privkey)
check(err)
sighash := hmac.New(sha512.New, secret_bytes)
sighash.Write([]byte(post_data))
sigstr := hex.EncodeToString(sighash.Sum(nil))
log.Println(sigstr)
j, err := json.Marshal(args)
check(err)
buf := bytes.NewBuffer(j)
req, err := http.NewRequest("POST", final_url, buf)
check(err)
req.Header.Set("Key", pubkey)
req.Header.Set("Sign", sigstr)
res, err := client.Do(req)
check(err)
defer res.Body.Close()
if res.StatusCode != 200 {
log.Println("bad status code")
log.Printf("%s", res)
panic(errors.New(res.Status))
}
res_body, err := ioutil.ReadAll(res.Body)
check(err)
//log.Printf("%v", res_body)
return dynjson.NewFromBytes(res_body)
}
CallPrivate("returnBalances", nil)

Right now I am stuck calling out to the python implementation which is not a pretty solution.

答案1

得分: 5

Python的dict是一种无序的映射类型。

>>> args = {}
>>> args['command'] = 'command'
>>> args['nonce'] = 10000
>>> list(args)
['nonce', 'command']  # <--- 迭代字典的顺序可能与插入顺序不同
                      #      无法保证按照插入顺序输出

urllib.urlencode不仅接受字典作为参数,还可以接受键值对序列的序列。你可以使用它来保证输出的顺序:

>>> import urllib
>>> urllib.urlencode({'command': 'command', 'nonce': 10000})
'nonce=10000&command=command'
>>> urllib.urlencode([('command', 'command'), ('nonce', 10000)])
'command=command&nonce=10000'

另外,你也可以使用collections.OrderedDict代替dict({}),它会记住键的插入顺序。

更新

在Go代码中,command的post数据是使用method而不是command参数设置的。

v.Set("command", method)
                 ^^^^^^

更新2

Python代码直接使用十六进制字符串secret

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

而Go代码在使用之前对其进行了解码:

secret_bytes, _ := hex.DecodeString(privkey)
sighash := hmac.New(sha512.New, secret_bytes)

请按照Python代码的方式进行操作:

sighash := hmac.New(sha512.New, []byte(privkey))

更新3

  • Go代码对post_data进行了json编组,但Python代码没有。
  • 你需要在Go代码中设置头部:"Content-Type=application/x-www-form-urlencoded"

以下是修改后的可以工作的Go代码:

func CallPrivate(method string, args map[string]string) {
    if args == nil {
        args = make(map[string]string)
    }
    v := make(url.Values)
    v.Set("nonce", "3000000000") //strconv.Itoa(int((time.Now().Unix()))*1000))
    v.Set("command", method)
    for k, val := range args {
        v.Set(k, val)
    }
    final_url := private_api_url
    post_data := v.Encode()
    sighash := hmac.New(sha512.New, []byte(privkey))
    sighash.Write([]byte(post_data))
    sigstr := hex.EncodeToString(sighash.Sum(nil))

    client := &http.Client{}
    buf := bytes.NewBuffer([]byte(post_data))
    req, _ := http.NewRequest("POST", final_url, buf)
    req.Header.Set("Key", pubkey)
    req.Header.Set("Sign", sigstr)
    req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
    res, _ := client.Do(req)
    defer res.Body.Close()

    res_body, _ := ioutil.ReadAll(res.Body)
    log.Printf("%s", string(res_body))
}
英文:

Python dict is an unordered mapping type.

&gt;&gt;&gt; args = {}
&gt;&gt;&gt; args[&#39;command&#39;] = &#39;command&#39;
&gt;&gt;&gt; args[&#39;nonce&#39;] = 10000
&gt;&gt;&gt; list(args)
[&#39;nonce&#39;, &#39;command&#39;]  # &lt;--- Iterating dictionary will yield in different order
#      with item insertion order

urllib.urlencode not only accepts a dictinoary, but it also accept a sequence of key-value-pair sequences. You can use it to guarantee the order of output:

&gt;&gt;&gt; import urllib
&gt;&gt;&gt; urllib.urlencode({&#39;command&#39;: &#39;command&#39;, &#39;nonce&#39;: 10000})
&#39;nonce=10000&amp;command=command&#39;
&gt;&gt;&gt; urllib.urlencode([(&#39;command&#39;, &#39;command&#39;), (&#39;nonce&#39;, 10000)])
&#39;command=command&amp;nonce=10000&#39;

Alternatively, instead of dict ({}), you can use collections.OrderedDict which remembers key insertion order.

UPDATE

In the Go code, command post data is set with method instead of command parameter.

v.Set(&quot;command&quot;, method)
^^^^^^

UPDATE2

Python code uses the hex string secret as is:

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

while the Go code decode it before use it:

secret_bytes, _ := hex.DecodeString(privkey)
sighash := hmac.New(sha512.New, secret_bytes)

Do the same way as the Python code:

sighash := hmac.New(sha512.New, []byte(privkey))

UPDATE3

  • Go code does json marshal for post_data, but Python code does not.
  • You need to set header in Go code: "Content-Type=application/x-www-form-urlencoded"

Here's the modified Go code that will work:

func CallPrivate(method string, args map[string]string) {
if args == nil {
args = make(map[string]string)
}
v := make(url.Values)
v.Set(&quot;nonce&quot;, &quot;3000000000&quot;) //strconv.Itoa(int((time.Now().Unix()))*1000))
v.Set(&quot;command&quot;, method)
for k, val := range args {
v.Set(k, val)
}
final_url := private_api_url
post_data := v.Encode()
sighash := hmac.New(sha512.New, []byte(privkey))
sighash.Write([]byte(post_data))
sigstr := hex.EncodeToString(sighash.Sum(nil))
client := &amp;http.Client{}
buf := bytes.NewBuffer([]byte(post_data))
req, _ := http.NewRequest(&quot;POST&quot;, final_url, buf)
req.Header.Set(&quot;Key&quot;, pubkey)
req.Header.Set(&quot;Sign&quot;, sigstr)
req.Header.Set(&quot;Content-Type&quot;, &quot;application/x-www-form-urlencoded&quot;)
res, _ := client.Do(req)
defer res.Body.Close()
res_body, _ := ioutil.ReadAll(res.Body)
log.Printf(&quot;%s&quot;, string(res_body))
}

huangapple
  • 本文由 发表于 2014年12月31日 09:49:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/27715162.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定