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

时间:2015-04-13 18:37:20

标签: ruby azure hmac sha256 azure-cosmosdb

我正在尝试按照Access Control跟踪文档并查询azure-documentdb-node SDK之类的代码,但我无法这样做。

我收到以下错误:401未经授权:{"代码":"未经授权","消息":"输入授权令牌可以&# 39;提供请求。请检查是否按照协议构建了预期的有效负载,并检查所使用的密钥。服务器使用以下有效负载进行签名:' post \ ndbs \ n \ n13 2015年4月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的建议和示例之后,我将代码简化为下面的snippit,它应该是他发布的节点代码的匹配但是它仍然在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的例子为例,我们可以修改他的节点代码:

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个编码的auth令牌不一样。 (Ryan再次非常感谢帮助我们做到这一点。)

2 个答案:

答案 0 :(得分:1)

我找到了答案。感谢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最后添加的换行符。

答案 1 :(得分:0)

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

在你的get_master_auth_token函数中,你似乎正在使用它来解码密钥。它是否正确?如果是这样,为什么?

这是一个node.js示例,它使用主密钥,构建auth头值并执行简单的http调用以列出数据库中的集合

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编码,但我认为你已经把它作为func的最后一行了。

我可以在你的代码和我的代码中发现的唯一区别是你似乎正在解码我不做的master_key。

我建议你做的是运行这个节点样本并比较我们在body和amp;中的字符串的值。签名到你的价值观。他们需要匹配。