创建HMAC签名的问题

时间:2016-08-08 11:11:29

标签: c azure openssl hmac

我需要在我用C编写的程序中使用Azure Blob存储。我没有找到任何可以使用它的C库,因此我决定使用Azure Blob Storage REST API编写自己的代码,curl和openssl库。

我找到了一些bash脚本,用于向Azure存储发出简单的请求,哪些方法运行良好并开始将其重写为C.

目前我在为请求创建HMAC签名时遇到了一些问题。我是密码学的新手,经过一番搜索,我找到了this

嗯,这是我的代码,它将对存储中的blob列表进行简单的GET请求:

#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#include <limits.h>
#include <openssl/evp.h>
#include <curl/curl.h>
#include <stdint.h>
#include <assert.h>

#include "crypto.h"


#define VERB_GET                               "GET"
#define VERB_POST                              "POST"
#define VERB_PUT                               "PUT"

#define AZURE_API_VERSION                      "2011-08-18"
#define AZURE_KEY_TYPE                         "SharedKey"

#define REQUEST_HEADER_X_MS_VERSION            "x-ms-version: "AZURE_API_VERSION
#define REQUEST_HEADER_X_MS_DATE_F             "x-ms-date: %s"
#define REQUEST_HEADER_AUTHORIZATION_F         "Authorization: "AZURE_KEY_TYPE \
                                               " %s:%s"


static const char *account_name = "<account_name>";
static const char *container_name = "<container_name>";
static const char *account_key = "<account_key>";



// at least 30 symbols for result must be allocated
static void get_time_now_gmt(char *result) {
    #define GMT_TIME_FORMAT_STRING "%s, %02d %s %d %02d:%02d:%02d GMT"

    static const char *nameOfDay[] = {
        "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
    };

    static const char *nameOfMonth[] = {
        "Jan", "Feb", "Mar", "Apr", "May", "Jun", 
        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
    };

    time_t t = time(NULL);
    struct tm tm = *gmtime(&t);

    // return something like Thu, 07 Jul 2016 11:07:53 GMT
    sprintf(result, GMT_TIME_FORMAT_STRING,
            nameOfDay[tm.tm_wday],          // Day of week name
            tm.tm_mday,                     // Day
            nameOfMonth[tm.tm_mon],         // Month name
            tm.tm_year + 1900,              // Year
            tm.tm_hour,                     // Hour
            tm.tm_min,                      // Minute
            tm.tm_sec);                     // Second
}


struct MemoryStruct {
  char *memory;
  size_t size;
};

static size_t curl_callback(void *contents, size_t size, size_t nmemb, 
        void* userp) {
    size_t realsize = size * nmemb;
    struct MemoryStruct *mem = (struct MemoryStruct *) userp;

    mem->memory = realloc(mem->memory, mem->size + realsize + 1);
    if (!mem->memory) {
        /* out of memory! */
        printf("not enough memory (realloc returned NULL)\n");
        return 0;
    }

    memcpy(&(mem->memory[mem->size]), contents, realsize);
    mem->size += realsize;
    mem->memory[mem->size] = 0;

    return realsize;
}

void make_curl_req(const char *h1, const char *h2, const char *h3) {
    CURL *curl_handle;
    CURLcode res;
    char url[256];
    struct MemoryStruct chunk;
    struct curl_slist *headers = NULL;

    chunk.memory = malloc(1); /* will be grown as needed by the realloc above */
    chunk.size = 0; /* no data at this point */

    curl_global_init(CURL_GLOBAL_ALL);

    /* init the curl session */
    curl_handle = curl_easy_init();

    sprintf(url, "https://%s.blob.core.windows.net/%s?restype=container&comp=list",
        account_name, container_name);

    headers = curl_slist_append(headers, "Accept:");
    headers = curl_slist_append(headers, h1);
    headers = curl_slist_append(headers, h2);
    headers = curl_slist_append(headers, h3);

    curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, headers);

    /* specify URL to get */
    curl_easy_setopt(curl_handle, CURLOPT_URL, url);

    /* send all data to this function  */
    curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, curl_callback);

    /* we pass our 'chunk' struct to the callback function */
    curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *) &chunk);

    /* get it! */
    res = curl_easy_perform(curl_handle);

    /* check for errors */
    if (res != CURLE_OK) {
        fprintf(stderr, "curl_easy_perform() failed: %s\n",
                curl_easy_strerror(res));
    } else {
        /*
         * Now, our chunk.memory points to a memory block that is chunk.size
         * bytes big and contains the remote file.
         *
         * Do something nice with it!
         */

        printf("%s\n", chunk.memory);

    }

    /* cleanup curl stuff */
    curl_easy_cleanup(curl_handle);

    free(chunk.memory);

    /* we're done with libcurl, so clean it up */
    curl_global_cleanup();
}

void print_it(const char* label, const unsigned char* buff, size_t len)
{
    if(!buff || !len)
        return;

    if(label)
        printf("%s: ", label);

    for(size_t i=0; i < len; ++i)
        printf("%02X", buff[i]);

    printf("\n");
}

int main(int argc, char **argv) {

    char request_date[31];
    char x_ms_date[45];
    char canonicalized_headers[45 + sizeof(REQUEST_HEADER_X_MS_VERSION)];
    char canonicalized_resources[strlen(account_name) + 
                                strlen(container_name) + 5];
    char string_to_sign[1024];

    unsigned char *decoded_hex_key;
    size_t decoded_hex_key_len;

    unsigned char *signature = NULL;
    size_t signature_len;

    unsigned char *signature_based;

    unsigned char auth_header[256];


    OpenSSL_add_all_algorithms();

    get_time_now_gmt(request_date);
    sprintf(x_ms_date, REQUEST_HEADER_X_MS_DATE_F, request_date);
    sprintf(canonicalized_headers, "%s\\n%s", 
            x_ms_date, REQUEST_HEADER_X_MS_VERSION);
    sprintf(canonicalized_resources, "/%s/%s", account_name, container_name);
    sprintf(string_to_sign, "%s\n\n\n\n\n\n\n\n\n\n\n\n%s\n%s\ncomp:list\nrestype:container",
            VERB_GET, canonicalized_headers, canonicalized_resources);

    printf("String to sign: %s\n", string_to_sign);

    base64_decode(account_key, &decoded_hex_key, &decoded_hex_key_len);

    print_it("Decoded hex key: ", decoded_hex_key, decoded_hex_key_len);

    hmac_sha256(decoded_hex_key, decoded_hex_key_len, &signature,
            &signature_len, decoded_hex_key, decoded_hex_key_len);

    free(decoded_hex_key);

    base64_encode(signature, signature_len, &signature_based);
    free(signature);

    sprintf(auth_header, REQUEST_HEADER_AUTHORIZATION_F, 
            account_name, signature_based);

    printf("Signature: %s\n", signature_based);

    free(signature_based);

//    make_curl_req(x_ms_date, REQUEST_HEADER_X_MS_VERSION, auth_header);

    return (EXIT_SUCCESS);
}

嗯,请求的结果是不正确的摘要。我尝试打印digest什么生成bash脚本和代码我找到的并且它们是不同的。

有人可以帮我解决这个问题,因为我没有想法。

P.S。即使我在代码中request_date和bash脚本中的request_date中保持不变的日期,创建的HMAC摘要也是不同的。

提前致谢。

更新:

好吧,好吧,我接下来从azure那里回忆:

<?xml version="1.0" encoding="utf-8"?><Error><Code>AuthenticationFailed</Code><Message>Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.
RequestId:71251945-0001-0020-1167-f16d5e000000
Time:2016-08-08T11:25:38.6226737Z</Message><AuthenticationErrorDetail>The MAC signature found in the HTTP request 'i4/LU2Rk8sBJ02kfokzzKz95QRUrOEDV8vwxNEo0uUA=' is not the same as any computed signature. Server used following string to sign: 'GET











x-ms-date:Mon, 08 Aug 2016 11:25:39 GMT
x-ms-version:2011-08-18
/<account_name>/<container_name>
comp:list
restype:container'.</AuthenticationErrorDetail></Error>

生成我的代码的签名是i4/LU2Rk8sBJ02kfokzzKz95QRUrOEDV8vwxNEo0uUA=,bash脚本是FZ/Kd4oQH7/aGqbgYBs0hsGIjm7JDcWQaGxkFlBKyrI=,用于request_date的相同值。

string_to_sign的值是下一个:

00000000  47 45 54 0a 0a 0a 0a 0a  0a 0a 0a 0a 0a 0a 0a 78  |GET............x|
00000010  2d 6d 73 2d 64 61 74 65  3a 4d 6f 6e 2c 20 30 38  |-ms-date:Mon, 08|
00000020  20 41 75 67 20 32 30 31  36 20 31 30 3a 34 31 3a  | Aug 2016 10:41:|
00000030  32 32 20 47 4d 54 0a 78  2d 6d 73 2d 76 65 72 73  |22 GMT.x-ms-vers|
00000040  69 6f 6e 3a 32 30 31 31  2d 30 38 2d 31 38 0a 2f  |ion:2011-08-18./|
00000050  3c 73 74 6f 72 61 67 65  5f 61 63 63 6f 75 6e 74  |<storage_account|
00000060  3e 2f 3c 63 6f 6e 74 61  69 6e 65 72 5f 6e 61 6d  |>/<container_nam|
00000070  65 3e 0a 63 6f 6d 70 3a  6c 69 73 74 0a 72 65 73  |e>.comp:list.res|
00000080  74 79 70 65 3a 63 6f 6e  74 61 69 6e 65 72        |type:container|
0000008e

我认为,问题在于将decoded_hex_key传递给hmac_sha256。也许我必须在传递之前进行转换?

1 个答案:

答案 0 :(得分:1)

我已经解决了我的问题。看起来我使用了不正确的签名创建方法,或使用它不正确(更有可能)。

所以,我找到了下一个方法,我相信的对某人有帮助。

static int hmac_sha25_digest(const u_char *msg, size_t mlen, const u_char *key, 
        size_t key_len, u_char **res, unsigned int *rlen) {

    *res = malloc((SHA256_DIGEST_LENGTH + 1) * sizeof(u_char));
    memset(*res, 0, (SHA256_DIGEST_LENGTH + 1) * sizeof(u_char));


    HMAC_CTX ctx;
    HMAC_CTX_init(&ctx);

    HMAC_Init_ex(&ctx, key, key_len, EVP_sha256(), NULL);
    HMAC_Update(&ctx, msg, mlen);
    HMAC_Final(&ctx, *res, rlen);
    HMAC_CTX_cleanup(&ctx);

    return 0;
}

轻松自如,满足我的所有需求。