-2

I'm taking an edx course called CS50. Maybe some of you are familiar with it.

One of the problems set asks you to implement an algorithm to crack a password that was hashed using des-based encryption and is up to 4 characters.

So far, so good. I've done it.

But I decided to improve it a little bit and make it possible to crack passwords that are up to 8 characters, which is the maximum for des-based encryption.

And the problem is, when I add the possibility of the fifth character (or more), my code doesn't work anymore.

Here are my codes

This one is working:

#define _XOPEN_SOURCE

#include <stdio.h>
#include <unistd.h>
#include <string.h>

/*
    Use this to compile 

    clang -ggdb3 -O0 -std=c11 -Wall -Werror -Wshadow crack4.c -lcrypt -lm -o crack4

*/


int main(int argc, char *argv[])
{
    if (argc != 2) //Checks if number of command-line arguments is valid
    {
        printf ("usage: ./crack + hash \n");
        return 1; //Retuns 1 (error)
    }

    char *hash = argv[1]; //Gets hash passed as argument

    char salt[3]; //Gets the salt
    salt[0] = hash[0];
    salt[1] = hash[1];
    salt[2] = '\0';


    //All possible characters used in a DES-based hashed password (taken from gnu library)
    const char *const seedchars = " ./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 

    char text[9] = "\0"; //Text that is gonna be tried with crypt()




    for (int d = 0; d <= 64 ; d++) //To check for passwords of up to 4 characters
    {
        printf("d %d \n", d);
        if(d > 0)
        {
            text[4] = '\0'; //Defining null-terminator at index 4    
            text[3] = seedchars[d]; //Iterates through the seedchars list at index 3
        }

        for (int c = 0; c <= 64 ; c++) //To check for passwords of up to 3 characters
        {
            if(c > 0) 
            {
                if (d == 0)
                {
                    text[3] = '\0'; //Defining null-terminator at index 3
                }
                text[2] = seedchars[c]; //Iterates through the seedchars list at index 2
            }

            for (int b = 0; b <= 64 ; b++) //To check for passwords of up to 2 characters
            {
                if(b > 0)
                {
                    if (c == 0 && d == 0)
                    {
                        text[2] = '\0'; //Defining null-terminator at index 2
                    }
                    text[1] = seedchars[b]; //Iterates through the seedchars list at index 1
                }

                for (int a = 0; a <= 64 ; a++) //To check for passwords of up to 1 character
                {
                    if(b == 0 && c == 0 && d == 0)
                    {
                        text[1] = '\0'; //Defining null-terminator at index 1
                    }

                    text[0] = seedchars[a]; //Iterates through the seedchars list at index 0

                    char *password = crypt(text, salt); //Hash var text and save it to var password

                    if (strcmp(hash, password) == 0)  //Compares the hash passed as argv with created above
                    {
                        printf("%s\n", text); //prints the text that led to said hash
                        return 0; //Returns 0 (okay)
                    }
                }   
            }   
        }
    }

    return 1; //Retuns 1 (error)
}

This one isn't working:

#define _XOPEN_SOURCE

#include <stdio.h>
#include <unistd.h>
#include <string.h>

/*
    Use this to compile 

    clang -ggdb3 -O0 -std=c11 -Wall -Werror -Wshadow crack.c -lcrypt -lm -o crack

*/


int main(int argc, char *argv[])
{
    if (argc != 2) //Checks if number of command-line arguments is valid
    {
        printf ("usage: ./crack + hash \n");
        return 1; //Retuns 1 (error)
    }

    char *hash = argv[1]; //Gets hash passed as argument

    char salt[3]; //Gets the salt
    salt[0] = hash[0];
    salt[1] = hash[1];
    salt[2] = '\0';


    //All possible characters used in a DES-based hashed password (taken from gnu library)
    const char *const seedchars = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 

    char text[9] = "\0"; //Text that is gonna be tried with crypt()

    for (int h = 0; h <= 64 ; h++) //To check for passwords of up to 8 characters
    {
        if(h > 0)
        {
            text[8] = '\0'; //Defining null-terminator at index 8    
            text[7] = seedchars[h]; //Iterates through the seedchars list at index 7
        }

        for (int g = 0; g <= 64 ; g++) //To check for passwords of up to 7 characters
        {
            if(g > 0)
            {
                if (h == 0)
                {
                    text[7] = '\0'; //Defining null-terminator at index 7    
                }
                text[6] = seedchars[g]; //Iterates through the seedchars list at index 6
            }

            for (int f = 0; f <= 64 ; f++) //To check for passwords of up to 6 characters
            {
                if(f > 0)
                {
                    if (g == 0 && h == 0)
                    {
                        text[6] = '\0'; //Defining null-terminator at index 6    
                    }
                    text[5] = seedchars[f]; //Iterates through the seedchars list at index 5
                }

                for (int e = 0; e <= 64 ; e++) //To check for passwords of up to 5 characters
                {
                    if(e > 0)
                    {
                        if (f == 0 && g == 0 && h == 0)
                        {
                            text[5] = '\0'; //Defining null-terminator at index 5    
                        }
                        text[4] = seedchars[e]; //Iterates through the seedchars list at index 4

                        for (int d = 0; d <= 64 ; d++) //To check for passwords of up to 4 characters
                        {
                            printf("d %d \n", d);
                            if(d > 0)
                            {
                                if (e == 0 && f == 0 && g == 0 && h == 0)
                                {
                                    text[4] = '\0'; //Defining null-terminator at index 4    
                                }
                                text[3] = seedchars[d]; //Iterates through the seedchars list at index 3
                            }

                            for (int c = 0; c <= 64 ; c++) //To check for passwords of up to 3 characters
                            {
                                if(c > 0) 
                                {
                                    if (d == 0 && e == 0 && f == 0 && g == 0 && h == 0)
                                    {
                                        text[3] = '\0'; //Defining null-terminator at index 3
                                    }
                                    text[2] = seedchars[c]; //Iterates through the seedchars list at index 2
                                }

                                for (int b = 0; b <= 64 ; b++) //To check for passwords of up to 2 characters
                                {
                                    if(b > 0)
                                    {
                                        if (c == 0 && d == 0 && e == 0 && f == 0 && g == 0 && h == 0) 
                                        {
                                            text[2] = '\0'; //Defining null-terminator at index 2
                                        }
                                        text[1] = seedchars[b]; //Iterates through the seedchars list at index 1
                                    }

                                    for (int a = 0; a <= 64 ; a++) //To check for passwords of up to 1 character
                                    {                                   
                                        if(b == 0 && c == 0 && d == 0 && e == 0 && f == 0 && g == 0 && h == 0)
                                        {
                                            text[1] = '\0'; //Defining null-terminator at index 1
                                        }

                                        text[0] = seedchars[a]; //Iterates through the seedchars list at index 0

                                        char *password = crypt(text, salt); //Hash var text and save it to var password

                                        if (strcmp(hash, password) == 0)  //Compares the hash passed as argv with created above
                                        {
                                            printf("%s\n", text); //prints the text that led to said hash
                                            return 0; //Returns 0 (okay)
                                        }
                                    }   
                                }   
                            }
                        }
                    }
                }
            }
        }
    }
    return 1; //Retuns 1 (error)
}

I'm using the hash below in both codes but it doesn't work on the second code.

hash - 50fkUxYHbnXGw
text - rofl  

Can someone please help me understand why is it not working?

Thanks.

Edit:

The second code just finished running and it actually is working but it's taking way longer to crack the password. Here's a screenshot:

I'm not being able to post it as a image for some reason, so here's the link https://i.stack.imgur.com/lBarv.jpg

Edit 2: adding link to image and fixing title

Edit 3: re-fixing title

Cam Moreira
  • 352
  • 1
  • 4
  • 13
  • 2
    I'm sorry, but "not working" is not a useful problem description. Nor is it a good fit for a Q&A site like SO. – StoryTeller - Unslander Monica Mar 09 '17 at 23:25
  • 1
    "it doesn't work..why is it not working"...."it actually is working"...probably should edit your question to just say what the problem is rather than that confusion. – kaylum Mar 09 '17 at 23:32
  • 1
    Fix your title: your code works, so your real question is why does it take so long. Fabio has answered that below. – TheGreatContini Mar 10 '17 at 02:16
  • DES is not limited to 8 characters (bytes actually), that is just the block size. DES as most block ciphers can encrypt essentially unlimited data lengths, one block at a time. – zaph Mar 18 '17 at 22:02

2 Answers2

3

In the first case, for 4 characters, you have 4 nested loops. Which "for" will execute up to 64 times. So your code may run 64 ^ 4 = 16 million times.

In the second case, for 8 characters, you have 8 nested loops. That makes 64 ^ 8 = 281 trillion times.

The time your computer takes to execute the algoritm is proportional to that amount of loops.

It grows so fast because your algorithm is exponential on the number of characters. If you want to learn more, search for "algorithm asymptotic notation".

  • I get that it should take longer to execute because it's 8 nested loops instead of 4. But the problem is, as seem in the image I posted, in the first code, d (the 4th loop) goes up to 50 and then it cracks the password. While in the second code, e (the 5th loop) goes up to 64, meaning that the loop before ran 64 ^ 2 = 128 times. Edit: adding information. – Cam Moreira Mar 10 '17 at 13:37
  • To make it more clear, you may add the "printf" bellow inside the inner loop (for(int a...)), to output every step. 'printf("h=%d g=%d f=%d e=%d d=%d c=%d b=%d a=%d \n", h, g, f, e, d, c, b, a);' If this takes too long to run, add a simple counter instead (like "total++;") and print it once at the end. Declare "total" as a **long** int. – Fabio Goncalves Mar 11 '17 at 16:40
  • I already did this. But only printing d and e in the 4-loops and 8-loops code, respectively. That's where that pic comes from and that how I know it is taking longer than it should to crack the same hash. – Cam Moreira Mar 13 '17 at 16:30
0

You can think of a - h as wheels of comprised of 65 characters, with indexes 0 through 64. The last character is a line terminator from the declared value.

If you were to add a printf for the value of e:

crack08 50fkUxYHbnXGw
e 1
d 0
d 1
d 2
d 3
d 4
d 5
d 6
d 7
d 8
d 9
d 10
d 11
d 12
d 13
d 14
d 15
d 16
d 17
d 18
d 19
d 20
d 21
d 22
d 23
...
d 63
d 64
e 2
d 0
d 1
d 2
...

You'd see we'd skip over the first value of e because h - f are 0.

Finding a 4 character match is then deferred until e = 64 (the 65th index the '\0' string terminator).

That represents more than 65 times longer before finding a 4 character password.

Getting rid of the extra passes involves reorganizing the 'wheel' of characters used, adding a '\0' character as the first character, using that as string terminator for the first pass and skipping over it for subsequent passes:

#define _XOPEN_SOURCE

#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char *argv[])
{
    if (argc != 2) {
        printf ("usage: ./crack  hash\n");
        return 1; //Retuns 1 (error)
    }
    char *hash = argv[1];
    char salt[3];
    salt[0] = hash[0];
    salt[1] = hash[1];
    salt[2] = '\0';

# define WHEEL_SIZE 65  // all possible password characters 
                        // plus null character for shorter strings

    char seedchars[WHEEL_SIZE] = 
        "@./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    char text[9];    // Text that is gonna be tried with crypt()
    text[8] = '\0';  // for h > 0
    seedchars[0] = '\0';  // test for short strings first
    int a_first = 0;
    int b_first = 0;
    int c_first = 0;
    int d_first = 0;
    int e_first = 0;
    int f_first = 0;
    int g_first = 0;

    for (int h = 0; h <= WHEEL_SIZE - 1 ; h++) {
        text[7] = seedchars[h];

        for (int g = g_first ; g <= WHEEL_SIZE - 1; g++) {
            text[6] = seedchars[g];

            for (int f = f_first; f <= WHEEL_SIZE -1; f++) {
                text[5] = seedchars[f];

                for (int e = e_first; e <= WHEEL_SIZE - 1; e++) {
                     printf("e %2d\n", e);
                    text[4] = seedchars[e];

                        for (int d = d_first; d <= WHEEL_SIZE - 1; d++) {
                            printf("d %2d\n", d);
                            text[3] = seedchars[d];

                            for (int c = c_first; c <= WHEEL_SIZE - 1; c++) {
                                if (c > 0) 
                                    b_first = 1;
                                text[2] = seedchars[c];
                                for (int b = b_first; b <= WHEEL_SIZE - 1; b++) {
                                        text[1] = seedchars[b];
                                    for (int a = a_first; a <= WHEEL_SIZE - 1; a++) {
                                        text[0] = seedchars[a];

                                        char *password = crypt(text, salt); 
                                        if (strcmp(hash, password) == 0) {
                                            printf("%s\n", text);
                                            return 0;
                                        }
                                    }
                                    b_first = 1;   
                                }
                                c_first = 1;
                            }
                            c_first = 1;
                        }
                        d_first = 1;
                    }
                    e_first = 1;
                }
                f_first = 1;
            }
            g_first = 1;
        }
    return 1;
}

And that gives us the answer in the minimum time:

crack 50fkUxYHbnXGw
e  0
d  0
d  1
d  2
d  3
d  4
d  5
d  6
d  7
d  8
d  9
d 10
d 11
d 12
d 13
d 14
d 15
d 16
d 17
d 18
d 19
d 20
d 21
d 22
d 23
d 24
d 25
d 26
d 27
d 28
d 29
d 30
d 31
d 32
d 33
d 34
d 35
d 36
d 37
d 38
d 39
d 40
d 41
d 42
d 43
d 44
d 45
d 46
d 47
d 48
d 49
d 50
rofl

A much more satisfactory answer.