使用 JavaScript 使用 HmacSHA256 正确签署字符串

Using JavaScript to properly sign a string using HmacSHA256

在 Houndify API 身份验证文档中,您有以下内容块:


验证请求的示例

假设我们有以下信息:

UserID: ae06fcd3-6447-4356-afaa-813aa4f2ba41
    RequestID: 70aa7c25-c74f-48be-8ca8-cbf73627c05f
    Timestamp: 1418068667   
    ClientID: KFvH6Rpy3tUimL-pCUFpPg==
    ClientKey: KgMLuq-k1oCUv5bzTlKAJf_mGo0T07jTogbi6apcqLa114CCPH3rlK4c0RktY30xLEQ49MZ-C2bMyFOVQO4PyA==
  1. 按以下格式连接 UserID 字符串、RequestID 字符串和 TimeStamp 字符串:{user_id};{request_id}{timestamp}

  2. 使用示例中的值,在这种情况下预期输出为:ae06fcd3-6447-4356-afaa-813aa4f2ba41;70aa7c25-c74f-48be-8ca8-cbf73627c05f1418068667

  3. 使用已解码的 ClientKey 对消息进行签名。结果是一个 32 字节的二进制字符串(我们无法用视觉表示)。然而,在 base-64 编码之后,签名是:myWdEfHJ7AV8OP23v8pCH1PILL_gxH4uDOAXMi06akk=

  4. 客户端然后生成两个认证headers Hound-Request-AuthenticationHound-Client-Authentication.

  5. Hound-Request-Authentication header 由 UserID 和 RequestID 拼接而成,格式如下:{user-id};{request-id}。继续上面的示例,此 header 的值将是: Hound-Request-Authentication: ae06fcd3-6447-4356-afaa-813aa4f2ba41;70aa7c25-c74f-48be-8ca8-cbf73627c05f

  6. Hound-Client-Authenticationheader由ClientID、TimeStamp字符串和签名串接而成,格式如下:{client-id};{timestamp};{signature}。继续上面的示例,此 header 的值将是:Hound-Client-Authentication: KFvH6Rpy3tUimL-pCUFpPg==;1418068667;myWdEfHJ7AV8OP23v8pCH1PILL_gxH4uDOAXMi06akk=


对于数字 3,它表示 "Sign the message with the decoded ClientKey"。 "message" 和 "ClientKey" 是两个不同的字符串。

我的问题:如何用一个字符串与另一个字符串签名,即这到底是什么意思?在 JavaScript 中你会怎么做?

var message = 'my_message';
var key = 'signing_key';

//??what next??

我正在尝试解决所有这些问题,以便我可以在 Postman 中创建一个 pre-request 脚本来执行正确的 HmacSHA256 哈希。

根据文档,如果您使用的是他们的 SDK 之一,它将自动验证您的请求:

SDKs already handle authentication for you. You just have to provide the SDK with the Client ID and Client Key that was generated for your client when it was created. If you are not using an SDK, use the code example to the right to generate your own HTTP headers to authenticate your request.

但是,如果您想手动执行,我相信您需要计算 HMAC value of the string they describe in the link in your question and then send it base64 encoded as part of the Hound-Client-Authentication header in your requests. They provide an example for node.js:

var uuid = require('node-uuid');
var crypto = require('crypto');

function generateAuthHeaders (clientId, clientKey, userId, requestId) {

    if (!clientId || !clientKey) {
        throw new Error('Must provide a Client ID and a Client Key');
    }

    // Generate a unique UserId and RequestId.
    userId      = userId || uuid.v1();

    // keep track of this requestId, you will need it for the RequestInfo Object
    requestId   = requestId || uuid.v1();

    var requestData = userId + ';' + requestId;

    // keep track of this timestamp, you will need it for the RequestInfo Object
    var timestamp   = Math.floor(Date.now() / 1000),  

        unescapeBase64Url = function (key) {
            return key.replace(/-/g, '+').replace(/_/g, '/');
        },

        escapeBase64Url = function (key) {
            return key.replace(/\+/g, '-').replace(/\//g, '_');
        },

        signKey = function (clientKey, message) {
            var key = new Buffer(unescapeBase64Url(clientKey), 'base64');
            var hash = crypto.createHmac('sha256', key).update(message).digest('base64');
            return escapeBase64Url(hash);

        },

        encodedData = signKey(clientKey, requestData + timestamp),
        headers = {
            'Hound-Request-Authentication': requestData,
            'Hound-Client-Authentication': clientId + ';' + timestamp + ';' + encodedData
        };

    return headers;
};

所以基本上,[在这种特定情况下]签名只是意味着除了 key 之外还使用散列算法创建字符串的散列,而不是 无密钥 散列[像 MD5]。例如:

var message = 'my_message';
var key = 'signing_key';
var hashed_message = hash_func(message, key);

其中 hash_func 是像 HmacSHA256 这样的哈希算法(有问题的哈希算法)。

我试图解决这个问题的原因是使用 Postman 测试 Houndify API 的身份验证。幸运的是,Postman 有一个很好的功能,叫做 pre-request scripts [complete with hashing algorithms] 如果你需要 pre-generate 需要与你一起发送的值,它会有所帮助要求。

经过多次尝试,我设法创建了一个 pre-request 脚本,可以正确验证此 API(请参阅下面的代码)。

var unescapeBase64Url = function (key) {
            return key.replace(/-/g, '+').replace(/_/g, '/');
        },

        escapeBase64Url = function (key) {
            return key.replace(/\+/g, '-').replace(/\//g, '_');
        },
        guid = function() {
    function s4() {
        return Math.floor((1 + Math.random()) * 0x10000)
        .toString(16)
        .substring(1);
    }
    return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
        s4() + '-' + s4() + s4() + s4();
    };

var client_id_str = environment["client-id"];
var client_key_str = environment["client-key"];
var user_id_str = environment["user-id"];
var request_id_str = guid();
var timestamp_str = Math.floor(Date.now() / 1000);

var client_key_dec_str = CryptoJS.enc.Base64.parse(unescapeBase64Url(client_key_str));

var message_str = user_id_str+";"+request_id_str+timestamp_str;
var signature_hash_obj = CryptoJS.HmacSHA256(message_str, client_key_dec_str);
var signature_str = signature_hash_obj.toString(CryptoJS.enc.Base64);

var hound_request_str = user_id_str+";"+request_id_str;
var hound_client_str = client_id_str+";"+timestamp_str+";"+escapeBase64Url(signature_str);

postman.setEnvironmentVariable("hound-request-authentication", hound_request_str);
postman.setEnvironmentVariable("hound-client-authentication", hound_client_str);

请注意,您必须在 Postman 中为 client-idclient-key 创建环境变量和 user-id,以及 header 变量 hound-request-authentication hound-client-authentication 保存定义 headers 时将引用的最终值。

希望对您有所帮助。