为什么Go生成的HMAC哈希与PHP和JavaScript不同?

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

Why does Go generate hmac hashes different than PHP and JavaScript?

问题

我开始使用Go语言编码,并尝试进行简单的请求验证,通过检查客户端发送的签名。乍一看一切都很好,但在验证真实请求时,经过几次测试后,我发现Go生成了一个奇怪的哈希值。

为了证明Go后端与JavaScript签名之间的不一致性,我开发了一个PHP版本的相同签名方法,并得到了与JavaScript版本相同的结果,所以我的期望是正确的。

我为每种语言开发了一个样本测试:GoPHPJavaScript

所以,为了在Go中实现与PHP和JavaScript相同的结果,我应该怎么做?


Go

package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/base64"
    "fmt"
)

func main() {
    data := "My name is Danniel"

    mac := hmac.New(sha256.New, []byte("secret"))
    mac.Write([]byte(data))

    macSum := mac.Sum(nil)
    data64 := base64.StdEncoding.EncodeToString(macSum)
    fmt.Println(fmt.Sprintf("mac: %s | b64: %s", macSum, data64))
    
    data64 = base64.StdEncoding.EncodeToString([]byte("My name is Danniel"))
    fmt.Println(fmt.Sprintf("b64: %s", data64))
}

输出

mac: 6��q��0��5й��|��G�#0��ih | b64: NqzRcf/FMLPvo678NdC58JB8lgOFR+wjMNQDDwSkaWg=
b64: TXkgbmFtZSBpcyBEYW5uaWVs

PHP

$data = 'My name is Danniel';

$macSum = hash_hmac('sha256', $data, 'secret');
$data64 = base64_encode($macSum);
echo sprintf('mac: %s | b64: %s', $macSum, $data64) . "\n";

$data64 = base64_encode('My name is Danniel');
echo sprintf('b64: %s', $data64) . "\n";

输出

mac: 36acd171ffc530b3efa3aefc35d0b9f0907c96038547ec2330d4030f04a46968 | b64: MzZhY2QxNzFmZmM1MzBiM2VmYTNhZWZjMzVkMGI5ZjA5MDdjOTYwMzg1NDdlYzIzMzBkNDAzMGYwNGE0Njk2OA==
b64: TXkgbmFtZSBpcyBEYW5uaWVs

JavaScript

var data = 'My name is Danniel';

var mac = CryptoJS.HmacSHA256(data, 'secret');
var macSum = mac.toString();
var data64 = btoa(macSum)
console.log('mac: ' + macSum + ' | b64: ' + data64);

var data64 = btoa('My name is Danniel')
console.log('b64: ' + data64);

输出

"mac: 36acd171ffc530b3efa3aefc35d0b9f0907c96038547ec2330d4030f04a46968 | b64: MzZhY2QxNzFmZmM1MzBiM2VmYTNhZWZjMzVkMGI5ZjA5MDdjOTYwMzg1NDdlYzIzMzBkNDAzMGYwNGE0Njk2OA=="
"b64: TXkgbmFtZSBpcyBEYW5uaWVs"
英文:

I'm starting to code in Go and I tried to do an simple request validation, by checking the signature sent by the client. At the first look everything was fine, but after a few tests validating real requests I found that Go is generating an awkward hash.

To prove the inconsistency between the Go backend and the JavaScript signature, I developed an PHP version of the same sign method, and it gave me identical results of the JavaScript version, so my expectation was right.

I've developed one sample test to each language: Go, PHP and JavaScript.

So, to achieve the same results of PHP and JavaScript in Go, what should I do?


Go

package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"fmt"
)

func main() {
	data := "My name is Danniel"

	mac := hmac.New(sha256.New, []byte("secret"))
	mac.Write([]byte(data))

	macSum := mac.Sum(nil)
	data64 := base64.StdEncoding.EncodeToString(macSum)
    fmt.Println(fmt.Sprintf("mac: %s | b64: %s", macSum, data64))
	
	data64 = base64.StdEncoding.EncodeToString([]byte("My name is Danniel"))
	fmt.Println(fmt.Sprintf("b64: %s", data64))
}

Output

mac: 6��q��0��5й��|��G�#0��ih | b64: NqzRcf/FMLPvo678NdC58JB8lgOFR+wjMNQDDwSkaWg=
b64: TXkgbmFtZSBpcyBEYW5uaWVs

PHP

$data = 'My name is Danniel';

$macSum = hash_hmac('sha256', $data, 'secret');
$data64 = base64_encode($macSum);
echo sprintf('mac: %s | b64: %s', $macSum, $data64) . "\n";

$data64 = base64_encode('My name is Danniel');
echo sprintf('b64: %s', $data64) . "\n";

Output

mac: 36acd171ffc530b3efa3aefc35d0b9f0907c96038547ec2330d4030f04a46968 | b64: MzZhY2QxNzFmZmM1MzBiM2VmYTNhZWZjMzVkMGI5ZjA5MDdjOTYwMzg1NDdlYzIzMzBkNDAzMGYwNGE0Njk2OA==
b64: TXkgbmFtZSBpcyBEYW5uaWVs

JavaScript

var data = 'My name is Danniel';

var mac = CryptoJS.HmacSHA256(data, 'secret');
var macSum = mac.toString();
var data64 = btoa(macSum)
console.log('mac: ' + macSum + ' | b64: ' + data64);

var data64 = btoa('My name is Danniel')
console.log('b64: ' + data64);

Output

"mac: 36acd171ffc530b3efa3aefc35d0b9f0907c96038547ec2330d4030f04a46968 | b64: MzZhY2QxNzFmZmM1MzBiM2VmYTNhZWZjMzVkMGI5ZjA5MDdjOTYwMzg1NDdlYzIzMzBkNDAzMGYwNGE0Njk2OA=="
"b64: TXkgbmFtZSBpcyBEYW5uaWVs"

答案1

得分: 5

问题在于你的 PHP 和 JavaScript 代码都执行了这样的操作:Base64(Hex(Hmac(key, msg)))。实际上,你并不需要进行双重编码。

在 PHP 中,你可以简单地请求 raw_encoding 而不是十六进制编码:

$macSum = hash_hmac('sha256', $data, 'secret', true);

而在 CryptoJS 中,你需要直接编码为 Base64,而不是先编码为十六进制,然后使用 btoa()

var data64 = mac.toString(CryptoJS.enc.Base64);

为此,你需要包含 enc-base64.js 组件。

<script src="https://cdn.rawgit.com/CryptoStore/crypto-js/3.1.2/build/rollups/hmac-sha256.js"></script>
<script src="https://cdn.rawgit.com/CryptoStore/crypto-js/3.1.2/build/components/enc-base64.js"></script>
英文:

The problem is that both your PHP and JavaScript codes do this: Base64(Hex(Hmac(key, msg))). You really don't need the double encoding.

In PHP you can simply request raw_encoding instead of hex:

<pre><code>$macSum = hash_hmac('sha256', $data, 'secret'<b>, true</b>);</code></pre>

and in CryptoJS, you have to encode directly to Base64 instead of encoding to hex and then using btoa() with:

var data64 = mac.toString(CryptoJS.enc.Base64);

For that, you will need to include the enc-base64.js component.

<!-- begin snippet: js hide: true -->

<!-- language: lang-js -->

var data = &#39;My name is Danniel&#39;;
  
var mac = CryptoJS.HmacSHA256(data, &#39;secret&#39;);
var macSum = mac.toString();
var data64 = mac.toString(CryptoJS.enc.Base64)
document.write(&#39;mac: &#39; + macSum + &#39; | b64: &#39; + data64);

var data64 = btoa(&#39;My name is Danniel&#39;)
document.write(&#39;&lt;br&gt;b64: &#39; + data64);

<!-- language: lang-html -->

&lt;script src=&quot;https://cdn.rawgit.com/CryptoStore/crypto-js/3.1.2/build/rollups/hmac-sha256.js&quot;&gt;&lt;/script&gt;
  &lt;script src=&quot;https://cdn.rawgit.com/CryptoStore/crypto-js/3.1.2/build/components/enc-base64.js&quot;&gt;&lt;/script&gt;

<!-- end snippet -->

答案2

得分: 2

你的 PHP 函数与 Go 函数的工作方式不同。

根据 PHP 文档:

返回一个包含计算得到的消息摘要的字符串,除非将 raw_output 设置为 true,否则返回小写的十六进制字符串表示形式,否则返回消息摘要的原始二进制表示。当 algo 未知时返回 FALSE。

所以在这种情况下,Go 是对 HMAC 摘要返回的原始字节进行编码。在 PHP(我猜测在 JavaScript 中也是如此),你是对 hmac 返回的十六进制字符串进行 base64 编码。

要在 Go 中实现相同的结果,需要先将 []byte 编码为十六进制,然后再进行 base64 编码。

然而,通常最佳实践是操作字节,而不是字节的编码。如果你必须遵循之前的设计,请继续使用,但否则你应该使用 PHP 函数中允许的 raw_output 选项,并且应该得到相同的结果。

英文:

Your PHP function works differently than the Go function.

From the PHP documentation:
> Returns a string containing the calculated message digest as lowercase
> hexits unless raw_output is set to true in which case the raw binary
> representation of the message digest is returned. Returns FALSE when
> algo is unknown.

So in this case, Go is encoding the raw bytes returned from the HMAC sum. In PHP (and I assume JS), you are b64 encoding a hex string returned from the hmac.

To achieve the same results in go, encode the []byte in hex, then base64 encode it.

Generally, however, it is best practice to operate on bytes, not an encoding of bytes. If you have to conform to a previous design, go ahead, but otherwise you should be using the raw_output option allowed in the PHP function and you should end up with the same results.

huangapple
  • 本文由 发表于 2016年2月24日 02:01:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/35584879.html
匿名

发表评论

匿名网友

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

确定