为什么 Go 生成的 hmac 哈希不同于 PHP 和 JavaScript?

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

我开始用 Go 编写代码,我尝试通过检查客户端发送的签名来进行简单的请求验证。乍一看一切都很好,但经过一些验证真实请求的测试后,我发现 Go 正在生成一个笨拙的哈希。

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

我为每种语言开发了一个示例测试:Go, PHP and JavaScript

那么,要在 Go 中达到 PHP 和 JavaScript 相同的结果,我应该怎么做?


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"

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

来自 PHP 文档:

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.

所以在这种情况下,Go 正在对从 HMAC 总和返回的原始字节进行编码。在 PHP(我假设是 JS)中,您正在对从 hmac 返回的十六进制字符串进行 b64 编码。

要在 go 中获得相同的结果,请将 []byte 编码为十六进制,然后进行 base64 编码。

但是,一般来说,最佳做法是对字节进行操作,而不是字节编码。如果你 符合以前的设计,继续,否则你应该使用 PHP 函数中允许的 raw_output 选项,你应该结束结果相同。

问题是您的 PHP 和 JavaScript 代码都这样做:Base64(Hex(Hmac(key, msg)))。你真的不需要双重编码。

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

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

并且在 CryptoJS 中,您必须直接编码为 Base64 而不是编码为十六进制,然后使用 btoa() 和:

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

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

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

var data64 = btoa('My name is Danniel')
document.write('<br>b64: ' + data64);
<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>