我需要在我用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
。也许我必须在传递之前进行转换?
答案 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;
}
轻松自如,满足我的所有需求。