Node Crypto createHmac()的输出与具有相同输入的PHP hash_hmac()输出不同吗



我正试图在Node中复制一个PHP哈希生成函数。此哈希用作API的一部分。PHP版本创建了系统可接受的正确输出。Node版本创建了不同的输出,尽管我认为函数上的输入是相同的。

这是因为PHP和Node HMAC函数有一些根本不同的工作方式吗?或者是因为字符编码的一些怪癖,我才错过了?或者我只是把别的事情搞砸了?


PHP代码

$url = 'https://example.com/api/endpoint';
$user = 'apiuser';
// Example key
$key = '+raC8YR2F+fZypNJ5q+CAlqLFqNN1AlAfWwkwJLcI7jrAvppjRPikWp523G/u0BLSpN9+2LusJvpSwrfU9X2uA==';
$timestamp = gmdate('D, d M Y H:i:s T', 1543554184); // gmdate('D, d M Y H:i:s T');
$hashdata = "GETn$urln$usern$timestampn";
print_r($hashdata);
/*
GET
https://example.com/api/endpoint
apiuser
Fri, 30 Nov 2018 05:03:04 GMT
*/
$decoded_key = base64_decode($key);
print_r(unpack('H*', $decoded_key));
// Array ( [1] => fab682f1847617e7d9ca9349e6af82025a8b16a34dd409407d6c24c092dc23b8eb02fa698d13e2916a79db71bfbb404b4a937dfb62eeb09be94b0adf53d5f6b8 )
$generated_hash = hash_hmac('sha256', $hashdata, $decoded_key, true);
$encoded_hash = base64_encode($generated_hash);
print_r($encoded_hash);
// vwdT8XhtSA1q+JvAfsRpJumfI4pemoaNFbjjc5JFsvw=

Node.js代码

crypto = require('crypto');
moment = require('moment-timezone');
let url = 'https://example.com/api/endpoint';
let api_user = 'apiuser';
// Example key
let api_key = '+raC8YR2F+fZypNJ5q+CAlqLFqNN1AlAfWwkwJLcI7jrAvppjRPikWp523G/u0BLSpN9+2LusJvpSwrfU9X2uA==';
let timestamp = moment.tz(1543554184 * 1000, 'GMT').format('ddd, DD MMM YYYY HH:mm:ss z'); // moment.tz(new Date(), 'GMT').format('ddd, DD MMM YYYY HH:mm:ss z');
let hash_data = 'GETn' + url + 'n' + api_user + 'n' + timestamp + 'n';
console.log($hashdata);
/*
GET
https://example.com/api/endpoint
apiuser
Fri, 30 Nov 2018 05:03:04 GMT
*/
let decoded_key = Buffer.from(api_key, 'base64').toString('utf8');
console.log(Buffer.from(api_key, 'base64'));
// <Buffer fa b6 82 f1 84 76 17 e7 d9 ca 93 49 e6 af 82 02 5a 8b 16 a3 4d d4 09 40 7d 6c 24 c0 92 dc 23 b8 eb 02 fa 69 8d 13 e2 91 6a 79 db 71 bf bb 40 4b 4a 93 ... >
const hmac = crypto.createHmac('sha256', decoded_key);
hmac.update(hash_data);
// Not sure which should be closest to PHP
// Or if there is a difference
let encoded_hash = hmac.digest('base64');
// let encoded_hash = Buffer(hmac.digest('binary')).toString('base64');
console.log(encoded_hash);
// hmac.digest('base64') == eLLVC9cUvq6Ber6t9TBTihSoq+2VWIMUJKiL4/fIj3s=
// Buffer(hmac.digest('binary')).toString('base64') == eLLVC9cUvq6Ber6t9TBTihSoq+2VWIMUJKiL4/fIj3s=

除了HMAC功能输出之外的所有内容似乎都是一样的。

操作系统:Windows 10-64位

Node.js版本:v10.13.0

PHP版本:7.2.7

我可以在Node.js中获得正确的结果,方法是将decoded_key保持为Buffer,并将其作为Buffer直接发送给crypto.createHmac:

let decoded_key = Buffer.from(api_key, 'base64');
const hmac = crypto.createHmac('sha256', decoded_key);

这是支持的,请参阅crypto.createHmac:

key <string> | <Buffer> | <TypedArray> | <DataView>

结果是vwdT8XhtSA1q+JvAfsRpJumfI4pemoaNFbjjc5JFsvw=-与PHP相同
工作示例:https://repl.it/repls/DisguisedBlankTechnologies

问题一定出在.toString('utf8')上。我没有找到另一种将其编码为字符串的方法,但它与Buffer一样有效。

为了完整起见,加密模块支持的另一个选项:

const hmac = crypto.createHmac('sha256', decoded_key);
hmac.write(hash_data);
hmac.end();
let encoded_hash = hmac.read().toString('base64');

工作示例:https://repl.it/repls/LightcoralUnwelcomeProfessionals

最新更新