3

I wrote a simple example with OpenSSL in C. I wanted to compute the MD4 hash value from my message, but I want to save result into a char array. Here’s my code with comments which will help you understand what I want to achieve:

#include <string.h>
#include <openssl/md4.h>
#include <stdio.h>

int main()
{
    unsigned char digest[MD4_DIGEST_LENGTH];
    char string[] = "hello world";

    // Run md4 for my message
    MD4((unsigned char*)&string, strlen(string), (unsigned char*)&digest);

    // Save md4 result into a char array. It doesn’t work
    char test[MD4_DIGEST_LENGTH];
    sprintf(test, "%02x", (unsigned int)digest);
    for(int i = 0; i < MD4_DIGEST_LENGTH; i++)
        printf("%02x", test[i]);
    printf("\n\n");

    // Print out md4 result. It works, but it's not into the char array as I wanted it to be
    for(int i = 0; i < MD4_DIGEST_LENGTH; i++)
        printf("%02x", digest[i]);
    printf("\n\n");

    // It works, but I don’t understand why 'mdString' is 33 size
    char mdString[33];
    for(int i = 0; i < MD4_DIGEST_LENGTH; i++)
    // And I also don’t get i*2 in this loop
         sprintf(&mdString[i*2], "%02x", (unsigned int)digest[i]);
    printf("md4 digest: %s\n", mdString);

    return 0;
}

Why does this code below not work? It shows a different MD4 value than it should be:

char test[MD4_DIGEST_LENGTH];

sprintf(test, "%02x", (unsigned int)digest);
for(int i = 0; i < MD4_DIGEST_LENGTH; i++)
    printf("%02x", test[i]);
printf("\n\n");

And how can I know what size should be mdString and why is there i*2 in the last loop?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
yak
  • 3,770
  • 19
  • 60
  • 111

1 Answers1

5

Firstly, your call to MD4() provides an incorrect address for the string and digest arrays: by using &, you are getting the array's address (char **), not the address of the first character. Since you are explicitly casting &string and &digest to unsigned char*, the compiler won't warn you. Remove the casts, and you will receive this warning:

warning: passing argument 1 of 'MD4' from incompatible pointer type

So instead call MD4() this way:

MD4(string, strlen(string), digest);

I personally prefer to avoid explicitly casting pointers unless it is really necessary, that way you will catch incorrect type casting much more easily.

Next, you attempt to use sprintf() to convert digest to a hexadecimal integer: sprintf(test, "%02x", (unsigned int)digest);. Two things wrong here: (a) since digest is essentially a character pointer, ie: memory address, you're turning this address into an unsigned integer and then turning that integer into a hex; (b) you need to loop over the elements of digest and convert each one into a character, snprintf won't do this for you!

I see that you may be relatively new to C given the mistakes made, but don't dispair, making mistakes is the way to learn! :)

If you can afford the book, I highly recommend "C Primer Plus" by Stephen Prata. It's a great intro for anyone starting out programming and it's a very complete reference for later use when you are already comfortable with the language. Otherwise, there is plenty of material online, and googling "C pointer tutorial" will return several useful results.

Hope this helps!


EDIT:

Forgot to comment about the other snippet of code that does work, but uses 33 bytes to store the string-ized MD4 hash:

// works but i dont understand why 'mdString' is 33 size
char mdString[33];
for(int i = 0; i < MD4_DIGEST_LENGTH; i++)
// and I also dont get i*2 in this loop
     sprintf(&mdString[i*2], "%02x", (unsigned int)digest[i]);
printf("md4 digest: %s\n", mdString);

The openssl manpage for MD4() states that the hash is 16 bytes long. Knowing this, and the fact that each unsigned char can hold a value from 0 to 255, then the maximum hexadecimal representation for any individual element in digest is 0xFF, in other words, 2 ASCII characters per unsigned char.

The reason the size for msString (33) appears cryptic is because MD4_DIGEST_LENGTH should have been used to calculate the size of the array: you need 2 characters to represent each one of the elements in digest + 1 null terminator ('\0') to end the string:

char mdString[(MD4_DIGEST_LENGTH * 2) + 1];

sprintf will print 2 characters to the mdString array whenever it's fed 1 byte from digest, so you need to advance 2 index positions in mdString for each position in digest, hence the use of i * 2. The following produces the same result as using i * 2:

for(int i = 0, j = 0; i < MD4_DIGEST_LENGTH; i++, j += 2)
     sprintf(&mdString[j], "%02x", (unsigned int)digest[i]);
easuter
  • 1,167
  • 14
  • 20
  • thank you! But could you please explain a little bit more converting `unsigned char digest` into `hexadecimal integer`? I dont know why should I use `i*2` in my `sprintf` loop and how can I know that resulting `mdString` should have 33 size? – yak Jul 13 '13 at 19:07
  • 3
    @yak, sorry about that, updated my original reply with the explanation about the cryptic array size. – easuter Jul 13 '13 at 21:48
  • thanks again! :) I have just the last question (hope so;) : when I want to measure performence of md5 for 512 input-size message, should I do it like this: `// assume that 'txt' has a length of 64, // so for char its 8 (char size) * 64 (text length) = 512 block size char txt = "..."; MD5(txt, strlen(txt),digest);` or like this: `char txt = "..."; // assume it has length of 512 MD5(txt, strlen(txt),digest);`. Asked about this also there: http://security.stackexchange.com/questions/38903/openssl-speed-block-size?lq=1 but anyone respond. Thanks :) – yak Jul 14 '13 at 14:43
  • I have some questions too (I am a newbie, just like yak). I dont get it why there are `2 ASCII characters per unsigned char`? And how do you know that `sprintf will print 2 characters to the mdString array whenever it's fed 1 byte from digest`? the rest is all clear, cheers guys:) – Brian Brown Jul 14 '13 at 16:00
  • 2
    @BrianBrown, as I mentioned in that section of my reply, an unsigned char can represent values between 0 and 255. **255 in hexadecimal is FF**, so 2 characters! Also, `sprintf` is being told to format the value with a leading zero, so 0 will be converted to 00, 1 will be 01, etc, all the way to FF. – easuter Jul 15 '13 at 08:30