无法使用 HMAC 在 Ruby 中生成正确的加密密钥

Unable to produce Proper Encryption Key in Ruby using HMAC

我正在尝试按照 Access Control and interrogating code like azure-documentdb-node SDK 的文档进行操作,但我无法做到。

我收到以下错误:401 Unauthorized: {"code":"Unauthorized","message":"输入的授权令牌无法满足请求。请检查预期的payload 是根据协议构建的,并检查正在使用的密钥。服务器使用以下 payload 进行签名:'post\ndbs\n\n13 april 2015 18:21:05 gmt\n\n'\r\nActivityId: ...

我的 ruby 代码如下所示:

require 'openssl'
require 'rest-client'
require 'base64'
require 'uri'
require 'json'
require 'time'

def get_databases url, master_key
  time = Time.now.httpdate
  authorization = get_master_auth_token "get", "", "dbs", time, master_key
  header = { "authorization" => authorization, "x-ms-date" => time, "x-ms-version" => "2015-04-08" }
  RestClient.get url, header
end

def get_master_auth_token verb, resource_id, resource_type, date, master_key
  digest = OpenSSL::Digest::SHA256.new
  key = Base64.decode64 master_key
  text = verb + "\n" +
    resource_type + "\n" +
    resource_id + "\n" +
    date + "\n" +
    "\n"
  hmac = OpenSSL::HMAC.digest digest, key, text.downcase
  auth_string = "type=" + "master" + "&ver=" + "1.0" + "&sig=" + hmac
  URI.escape auth_string
end

谢谢!

编辑:在 Ryan 的建议和示例之后,我将代码简化为以下片段,该片段应该与他发布的节点代码相匹配,但它仍然在 ruby 中失败:

def hard_coded_get_databases master_key, url
  verb = "get"
  resource_type = "dbs"
  resource_id = ""
  date = Time.now.httpdate
  serv_version = '2014-08-21'
  master_token = "master"
  token_version = "1.0"
  key = Base64.decode64 master_key
  text = verb + "\n" + resource_type + "\n" + resource_id + "\n" + date + "\n\n"
  body = text.downcase.force_encoding "utf-8"
  signature = OpenSSL::HMAC.digest OpenSSL::Digest::SHA256.new, key, body
  auth_token = URI.escape("type="+master_token + "&ver="+token_version + "&sig="+signature)

  header = { "accept" => "application/json", "x-ms-version" => serv_version, "x-ms-date" => date, "authorization" => auth_token }
  RestClient.get url, header
end

EDIT2:我相信我已经将问题与我如何进行主密钥身份验证隔离开来。

以 Ryan 为例,我们可以 trim 他的节点代码如下:

var crypto = require("crypto")

function encode_message(masterKey, message) {
    var key = new Buffer(masterKey, "base64"); // encode/decode? base64 the masterKey
    var body = new Buffer(message.toLowerCase(), "utf8"); // convert message to "utf8" and lower case
    return crypto.createHmac("sha256", key).update(body).digest("base64"); // encrypt the message using key
 }

如果我调用此节点代码,我可以生成以下密钥:

encode_message("blah", 'get\ncolls\n\nTue, 14 Apr 2015 13:34:22 GMT\n\n')
'IYlLuyZtVLx5ANkGMAxviDHgC/DJJXSj1gUGLvN0oM8='

如果我生成等效的 ruby 代码来创建身份验证,我的 ruby 代码如下所示:

require 'base64'
require 'openssl'

def encode_message master_key, message
  key = Base64.urlsafe_decode64 master_key
  hmac = OpenSSL::HMAC.digest 'sha256', key, message
  Base64.urlsafe_encode64 hmac
end

如果我调用这段代码,我会得到以下结果:

2.2.1 :021 > encode_message("blah", "get\ncolls\n\nTue, 14 Apr 2015 13:34:22 GMT\n\n")
 => "N6BL3n4eSvYA8dIL1KzlTIvR3TcYpdqW2UNPtKWrjP8="

很明显,2 个编码的身份验证令牌不一样。 (Ryan 再次非常感谢您帮助我们走到这一步)。

首先,我会为我有限的 Ruby 知识而道歉,但让我在这里尝试提供帮助;

在您的 get_master_auth_token 函数中,您似乎在使用密钥之前对其进行解码。这样对吗?如果是,为什么?

这里是一个 node.js 示例,它使用主密钥,建立 auth header 值并执行一个简单的 http 调用以在数据库 [=11] 中列出 collections =]

var crypto = require("crypto");
var https = require("https");

https.globalAgent.options.secureProtocol = "TLSv1_client_method";

var verb = 'get'; 
var resourceType = 'dbs'; //the resource you are trying to get. dbs, colls, docs etc. 
var resourceId = ''; //the parent resource id. note: not the id, but the _rid. but for you, because you are trying to lookup list of databases there is no parent
var masterKey = '...'; //your masterkey 
var date = new Date().toUTCString();

var auth = getAuthorizationTokenUsingMasterKey(verb, resourceId, resourceType, date, masterKey);

var options = {
hostname: '...', //your hostname (without https://)
port: 443,
path: '/dbs/',
method: 'GET',
    headers: {
        accept: 'application/json',
        'x-ms-version': '2014-08-21',
        'x-ms-date': date,
        authorization: auth,
    }
};

for (var i = 0; i < 1000; i++) {
    var req = https.request(options, function (res) {
        process.stdout.write(new Date().toUTCString() + " - statusCode: " + res.statusCode + "\n");
        res.on('data', function (d) {
        }).on('error', function (e) {
        })
    });

    //console.log(req);

    req.end();
}

function getAuthorizationTokenUsingMasterKey(verb, resourceId, resourceType, date, masterKey) {
    var key = new Buffer(masterKey, "base64");

    var text = (verb || "") + "\n" + 
        (resourceType || "") + "\n" + 
        (resourceId || "") + "\n" + 
        (date || "") + "\n" + 
        ("") + "\n";

    var body = new Buffer(text.toLowerCase(), "utf8");
    var signature = crypto.createHmac("sha256", key).update(body).digest("base64");

    var MasterToken = "master";    
    var TokenVersion = "1.0";

    return encodeURIComponent("type=" + MasterToken + "&ver=" + TokenVersion + "&sig=" + signature);
}

在您的示例中,传递给 getAuthorizationTokenUsingMasterKey 方法的 resourceId 应该是“”,resourceType 应该是您拥有的 "dbs"。

我确实注意到在某些情况下您必须对值进行 URI 编码,但我认为您已经在函数的最后一行这样做了。

我能在你的代码和我的代码中发现的唯一区别是你似乎在解码我没有解码的 master_key。

我建议您做的是 运行 这个节点示例,并将我们在 body 和签名中的字符串值与您的值进行比较。他们需要匹配。

我找到了答案。感谢 Magnus Stahre ...他是帮助我解决问题的人。

这就是我所想的编码,技巧是这样的:

def encode_message master_key, message
  key = Base64.urlsafe_decode64 master_key
  hmac = OpenSSL::HMAC.digest 'sha256', key, message.downcase
  Base64.encode64(hmac).strip
end

我过早地在我的代码中缩小了大小写,而且我的 Base64.encode64 未能去掉 ruby 最后添加的换行符。