hes_theman
hes_theman

Reputation: 628

Understand crypt()'s output in case of MD5

The glibc version of crypt(3) function supports MD5 algorithm. According to the documentation:

If salt is a character string starting with the characters "$id$"
followed by a string optionally terminated by "$", then the result
has the form:

       $id$salt$encrypted

The document also states that in case of MD5, $id must be 1 and size of the encrypted string is fixed to 22 characters.

But MD5 output is always 128-bit length. So how does it come to only 22 characters?

I thought it would be Base64-encoded, since 128 bits can represent 22 Base64 characters. Let's verify my assumption.

Below is simple code I wrote to call crypt() with MD5, no salt used for encryption:

#define _XOPEN_SOURCE
#include <unistd.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
    char md5[] = "$1$";
    char *res;
    res = crypt("helloworld", md5);
    printf("res = %s\n", res);
    return 0;
}

Run it:

$ ./crypt
res = $1$$edK86ZB1Vvaz2eneY.itb.

The MD5 encrypted value of helloworld according to crypt() is edK86ZB1Vvaz2eneY.itb..

Now calculate the MD5 hash of helloworld by an online tool or by md5sum:

$ echo -n helloworld | md5sum
fc5e038d38a57032085441e7fe7010b0  -

The MD5 hash value is fc5e038d38a57032085441e7fe7010b0. Convert this hex string to Base64 by this online converter or by following command:

$ echo "fc5e038d38a57032085441e7fe7010b0" | xxd -r -p | base64
/F4DjTilcDIIVEHn/nAQsA==

This value is different than the value that crypt() produced above.

So how does crypt() produce its output in this case or how am I supposed to interpret it? What am I missing here?

Upvotes: 0

Views: 1174

Answers (1)

KamilCuk
KamilCuk

Reputation: 141708

It uses the MD5 algorithm, but does not return the generated MD5 hash. There are many weird things going on in the source, from here:

/* The original implementation now does something weird: for every 1
   bit in the key the first 0 is added to the buffer, for every 0
   bit the first character of the key.  This does not seem to be
   what was intended but we have to follow this to be compatible.  */

or

/* Now comes another weirdness.  In fear of password crackers here
   comes a quite long loop which just processes the output of the
   previous round again.  We cannot ignore this here.  */

__md5_crypt_r is called from __crypt_r from here.

It uses the MD5 algorithm, but it does not return the md5 hash in the user string. Instead it does multiple transformations to "encrypt" the input string.

For md5 hashes, use openssl/md5.h. I guess I would advise against using the encryption family functions from libc, unless writing something really small and not portable. From the html documentation:

[...] the DES uses only a 56-bit key (plus 8 parity bits), and a machine has been built in 1998 which can search through all possible keys in about 6 days, which cost about US$200000; faster searches would be possible with more money. This makes simple DES unsecure for most purposes, and NIST no longer permits new US government systems to use simple DES.

Upvotes: 3

Related Questions