If you’ve been following my recent posts, you’re probably well aware that I’ve been doing a lot of playing with Web APIs–particularly with iTunes and Amazon Web Services. I love the concept of API as User Interface and I love the new ways established companies are providing Web-based utilities.
Because the World Wide Web is such an open place, many Web API services demand that you authenticate your requests. Requests must come from the proper parties and sent without loss of message integrity.
Cryptographic hash functions like SHA-1 (a Secure Hash Algorithm) allow you to electronically “sign” your requests with a personal ID key. Amazon, for example, assigns you both an access key and a secret access key. The former identifies you publicly, and the latter identifies your signed requests.
Here’s the code I use to sign my Amazon Web Services headers using SHA-1. I then produce a Base64 rendition of that hash.
You can find your own Amazon Web Services keys by visiting the AWS homepage and hover over the peach-colored “Your Web Services Account” button on the top-left of the page. Chose View Access Key Identifiers from the pop-up.
As with all my code, caveat emptor. You can download this source here.
// From Erica Sadun, 15 March 2006
// cc signheader.c -w -o signheader -lcurl -lcrypto
#include <stdio.h>
#include <string.h>
#include <openssl/hmac.h>
#include <openssl/evp.h>
#include <openssl/bio.h>
#include <curl/curl.h>
#define SECRETKEY "PUT KEY HERE"
#define SLEN 1024
main(argc, argv)
int argc;
char *argv[];
{
int riz, howlong;
unsigned char headertext[SLEN], signature[SLEN];
if (argc < 2)
{
printf("Usage: %s uri-encoded-headersn", argv[0]);
exit(-1);
}
// Decode header
sprintf(headertext, "%s", curl_unescape(argv[1], strlen(argv[1])));
// Encrypt it
doEncrypt(headertext, signature);
printf("%sn", signature);
}
void doEncrypt(kString, sigString)
unsigned char *kString;
char *sigString;
{
HMAC_CTX hctx;
BIO *bio, *b64;
char *sigptr;
long siglen = -1;
char *signature = NULL;
unsigned int rizlen;
unsigned char skey[SLEN], results[SLEN];
// Initialize SHA1 encryption
sprintf(skey, "%s", SECRETKEY);
HMAC_CTX_init(&hctx);
HMAC_Init(&hctx, skey, (int)strlen((char *)skey), EVP_sha1());
// Encrypt
HMAC(EVP_sha1(), skey, (int)strlen((char *)skey),
(unsigned char *)kString, (int)strlen((char *)kString),
results, &rizlen);
// Base 64 Encode
b64 = BIO_new(BIO_f_base64());
bio = BIO_new(BIO_s_mem());
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
bio = BIO_push(b64, bio);
BIO_write(bio, results, rizlen);
BIO_flush(bio);
siglen = BIO_get_mem_data(bio, &sigptr);
signature = malloc(siglen+1);
memcpy (signature, sigptr, siglen);
signature[siglen] = '\0';
sprintf(sigString, "%s", signature);
// Clean up Encryption, Encoding
BIO_free_all(bio);
HMAC_CTX_cleanup(&hctx);
}


I've not used the APIs that you're referring to. But if I understand your code, then I feel that I must object to your use of the term "encrypt" in both your function name and your comments. An encrypt operation implies that there is a corresponding decrypt operation. But in this case there is not.
This is really cryptographic signing. When the server receives your call, it will cryptographically sign the very same data block, which it can do since it too knows your secret key. If their result comes out the same, then they know that you too knew the secret key, and have thereby authenticated you. But the important point is is that they're not decrypting what you did, they're re-doing it and then comparing.
And how about some maintable commented code? I suspect even you in six months time will be unclear as to what's going on. For example, what is a "riz" as in "rizlen"?
Hi Erica,
It seems we have a similar interest in this general area. Have a look at this post from the beginning of April... would LOVE to see another hacker such as yourself take something like this and see what, if anything, could realistically be integrated into the existing world of web servers, with as little effort as possible as it seems to me that if this could be accomplished, some pretty neat things could be the result.
I have more to add to this post myself in the upcoming weeks, so if nothing else, I will update this post with a comment when I do.
> http://www.oreillynet.com/xml/blog/2006/04/mapping_data_between_domains_a.html
To Bob:
Regarding the maintainable commented code remark, please keep in mind that this is a blog post meant to demonstrate some code. When one adds code to be executed or compiled it's generally expected and nice to keep the code as simple as possible.
Perhaps Erica could provide a more maintainable copy of the code, but it's all her call. Your sarcasm is a abit of an affront to her choice to post something interesting.
You've got a point about the difference between a hash computation and encryption, but it's really semantics. The important point isn't what you mentioned as the important point, it is that Erica has cooked up a simple application for her that she can use to get things done. I believe it's more of a "you say po-tay-toe; I say po-tah-toe" circumstance than anything.
I believe that "rizlen" is the lenth of the "results" array.
To Erica: Would you mind pointing us in the direction of some documentation for the APIs you're using?
Bob & Josh: My bad. You're totally right on the fact that although it's a cryptographic function, it's not encryption. I will now smack my head against the wall several times. As for "riz"/"rizlen", it's become part of my programming idiom for so many years that I always know what it means.
Bob: What part of the code would you suggest needs more commenting? I've looked it over and I don't think I'd add more on a personal level, but I'd love to hear where you'd put it.
MDavid: I'm totally into putting together a group collection of tools. More about that later.
Josh: You can read about most of the Amazon API at http://developer.amazonwebservices.com. The iTunes API, I'm reverse engineering, as I've been doing with Pandora as well.
Everyone: I think I'm missing a BIO_free_all(b64); in there.
Hey Erica,
Excellent! Ill look forward to hearing more :)
Interesting comments.. :D
hey, thanks for putting this together in a nice googleable snippet.
For people who might copy the code directly though, I feel obligated to point out that the doEncrypt() function as written will result in memory leaks since the memory malloc'd for signature is never freed.
A simple way to remedy it is to just remove the malloc, since it's redundant.
This snippet:
siglen = BIO_get_mem_data(bio, &sigptr);
signature = malloc(siglen+1);
memcpy (signature, sigptr, siglen);
signature[siglen] = '\0';
sprintf(sigString, "%s", signature);
Could be replaced simply with this:
siglen = BIO_get_mem_data(bio, &sigptr);
memcpy (sigString, sigptr, siglen);
sigString[siglen] = '\0';
and behave exactly the same. However, I also want to point out to others that it is very dangerous as written since the string the result is being written to, the parameter "char * sigString", has no known length in the doEncrypt function and could result in buffer overflows & security exploits. I would suggest either adding a length parameter to the function & changing the memcpy to not exceed this length, or changing the parameter to a char **, allocating the memory within doEncrypt, and then freeing it within the calling function or elsewhere.
Also, erica mentioned the possible need for a BIO_free_all(b64). This is not necessary, as the line BIO_push(b64, bio) appends b64 to bio in a "BIO chain" (assuming it was successful). Calling BIO_free_all(bio) as this code does, frees the entire BIO chain, including the memory allocated for bio and b64.
See: http://www.openssl.org/docs/crypto/BIO_new.html
Hi, not that I insist or what, but... I wonder whether you use actually code mentioned above in real life. Because if you do use it, then getting your binary which executes that code is like looking under your door carpet, if you understand what I mean ;)