0

I'm writing a program for a class that cracks a 5 character (aA-zZ) password by collecting a hashed password at the command prompt and matching the hash with hashes generated with crypt() function. My problem is adding the 5th loop to crack a 5 character password slows the program to a halt, even for 1-4 character passwords, and I have to cancel. Removing 5th loop cracks 1-4 character passwords in under a minute.

We have only covered the basics in class which is demonstrated in my code. So the solution must match this coding level.

#include <cs50.h>
#include <stdio.h>
#include <crypt.h>
#include <string.h>

void printpass (string fst, string hsh1, string hsh);
/* prompt user for 1 cmd line argument */
int main(int argc, string argv[])

{    
    /* only allow 1 cmd line argument and return cmd line error
    and exit if false */    
    if (argc != 2)
    {
        printf("Invalid request!\n");
            return 1;
    }
    string hash = argv[1];
    char slt1 = hash[0];
    char slt2 = hash[1];
    char salt[3] = {slt1, slt2, '\0'};
    char alpha[52] = "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm";//upper & lowercase passwords only

    for (int d = 0; d < 52; d++) //search for one character pw
    {
        char s1 = alpha[d];
        char first1[2] = {s1, '\0'};
        string hash0 = crypt(first1, salt);
        printpass (first1, hash0, hash);

        if (strcmp(hash0, hash) != 0) 
        for (int f = 0; f < 52; f++) //search for two character pw
        {
            char s2 = alpha[f];
            char first2[3] = {s1, s2, '\0'};
            string hash1 = crypt(first2, salt);
            printpass (first2, hash1, hash);

            if (strcmp(hash1, hash) != 0 || strcmp(hash0, hash) != 0)
            for (int h = 0; h < 52; h++) //search for two character pw
                {
                    char s3 = alpha[h];
                    char first3[4] = {s1, s2, s3, '\0'};
                    string hash2 = crypt(first3, salt);
                    printpass (first3, hash2, hash);

                    if (strcmp(hash2, hash) != 0 || strcmp(hash1, hash) != 0 || strcmp(hash0, hash) != 0)
                    for (int j = 0; j < 52; j++) //search for two character pw
                        {
                            char s4 = alpha[j];
                            char first4[5] = {s1, s2, s3, s4, '\0'};
                            string hash3 = crypt(first4, salt);
                            printpass (first4, hash3, hash);

                            if (strcmp(hash3, hash) != 0 || strcmp(hash2, hash) != 0 || strcmp(hash1, hash) != 0 || strcmp(hash0, hash) != 0)
                            for (int l = 0; l < 52; l++) //search for two character pw
                                {
                                    char s5 = alpha[l];
                                    char first5[6] = {s1, s2, s3, s4, s5, '\0'};
                                    string hash4 = crypt(first5, salt);
                                    printpass (first5, hash4, hash);

                                }
                            }
                }
        }
    }
}

void printpass (string fst, string hsh1, string hsh) //compare hashes and print if true
{
    if (strcmp(hsh1, hsh) == 0)
    {
        printf("%s", fst);
    }
}

1-4 character passwords should complete in under a minute with the 5th loop in the code.

  • Is this compiled as c++? If so, I recommend the c++ tag. Asking because I saw a `string` declaration – axelduch Oct 17 '19 at 18:47
  • 1
    @axelduch: This program includes the infamous `` header, which makes `string` a typedef for `char *`. – M Oehm Oct 17 '19 at 18:56
  • Side comment - you can improve performance by removing some of the redundant strcmp. Also, consider breaking the loops, when a match is found., – dash-o Oct 17 '19 at 19:07
  • aaah didn't know about that thanks for that! @MOehm – axelduch Oct 17 '19 at 19:18
  • @dash-ohow do i break a loop when a match is found? I tried adding break; after each if statement and changing the statements to == instead of != but it didn't interrupt the loop. – Sam Shields Oct 17 '19 at 21:28

4 Answers4

0

4 characters long password will take up to 7311616 (or 52^4) iterations to crack, and 5 characters long password will take up to 380204032 (or 52^5) iterations.

axelduch
  • 10,769
  • 2
  • 31
  • 50
0

The code run 5 nested loops (d, f, j, h, l). Trying Q, QQ, QQQ, QQQQ, QQQQQ, QQQQW, ..., trying all combinations (with various length) of passwords starting with Q, before attempting to evaluate any password starting with any other letter.

While there are about 7.2M 4 letter combinations (52^4), the code will evaluate many 5 letter combinations (there are about 380M of them) before trying the complete smaller subset of 4 letter combination. Even for 1 letter password, (e.g., A), the code will evaluate almost half of the 5 letter combinations, before trying the simple 'A'.

As an alternative, consider reordering the loops:

  1. single letter passwords
  2. 2 letter passwords
  3. 3 letter passwords
  4. 4 letter passwords
  5. 5 letter passwords
dash-o
  • 13,723
  • 1
  • 10
  • 37
  • The loops are already ordered from least to greatest as you suggested but I forgot to update the comments after pasting. Is there anything else I can do to speed up the program, – Sam Shields Oct 17 '19 at 20:00
  • @SamShields - can you post the updated code. The current code evaluate 5 character passwords before completing all 4 character password. You can add unconditional print of the key in printpass to validate order: Q, QQ, QQQ, QQQQ, QQQQQ, QQQQW, ... 7.1m of passwords with 5 letter starting with Q here ..., then the same with the first Q replaced by W. – dash-o Oct 17 '19 at 20:38
  • @SamShields what order to you expect the program to check ? – dash-o Oct 17 '19 at 20:39
  • Isn't the current code built from least to greatest? If you check you'll see the top loop checks for the first character and each loop after increases by 1 character. – Sam Shields Oct 17 '19 at 21:25
  • @SamShields Short Answer: NO. The 'f' loop is nested in the 'd' loop. Meaning that all values for 'f' will be checked (QQ, QW, QW) before that code will check for 'W'. You will need to reorg the loops to get the order that you want (shorter passwords first). Consider adding unconditional print to printpass to see the actual order that password are evaluated – dash-o Oct 18 '19 at 04:30
  • I was able to increase the speed of the program by creating a second loop. So there is a nested 4 and nested 5 loop in the same program. The program first checks the nested 4 and if the password is 1-4 characters then it completes within 30 seconds. If the password is 5 characters then it moves on to the second loop and cracks the 5 character password in 12 minutes. – Sam Shields Oct 19 '19 at 02:14
  • @SamShields, happy to hear you got a solution. If this answer or any other one helped you solved your issue, consider 'accepting' that answer. – dash-o Oct 19 '19 at 04:15
0

From comments, looks like the required order is shorter passwords will be tested before longer password. This will result in the following sequence: (Q, W, E, ... QQ, QW, QE, ..., WQ, WW, WE, ..., QQQ, QQE, ...), instead of the current dictionary order (Q, QQ, QQQ, QQQQ, QQQQQ, QQQQW, QQQQE, ...).

With MINIMAL changes to the current code, this can be achieved with the following. See below for additional proposal that will require more than minimal changes.

   bool done = false ;
   for (int pwlen=1 ; pwlen<=5 && !done ; pwlen++ ) {
       for (int d = 0; d < 52 && done; d++) {
           char s1 = alpha[d];

           if ( pwlen == 1 ) {
               char first1[2] = {s1, '\0'};
               string hash0 = crypt(first1, salt);
               printpass (first1, hash0, hash);
               if (strcmp(hash0, hash) == 0 ) { done = true ; break } ;
           } else {
               for (int f = 0; f < 52 && done; f++) {
               ... same changes ...
               if ( pwlen == 2 ) {
               } else {
                  ... level 3 here.
               } ;
           } ;
       } ;
   } ;

Also consider the following changes:

  1. Moving the whole logic from 'main' into a different function that will be called by main. It will make it easier to modify the code (e.g., use 'return' to break from internal loops, etc). The function should RETURN the matched key.
  2. Changing printpass to return true/false indication if the password match. It will eliminate redundant strcmp.
dash-o
  • 13,723
  • 1
  • 10
  • 37
0

as noted in the other answers, the combinatorial complexity of this sort of problem makes it very hard. hopefully you can see why password length recommendations are generally "something more than 8 letters"

that said, people don't pick characters (uniformly) randomly. hence why password cracking tools such as John the Ripper use dictionary attacks and statistical observations in an attempt to speed up the process. the program can also use multiple CPU and GPU cores where possible so churn through things faster

as a comment on the style of your code, all your nested loops look very similar. I'd recommend turning this into a recursive function, it should be pretty easy and should cut down on the code significantly. most of the time will be spent running crypt(3) so you shouldn't need to worry much about performance of your code but it might be worth profiling just to make sure

Sam Mason
  • 15,216
  • 1
  • 41
  • 60
  • I was able to increase the speed of the program by creating a second loop. So there is a nested 4 and nested 5 loop in the same program. The program first checks the nested 4 and if the password is 1-4 characters then it completes within 30 seconds. If the password is 5 characters then it moves on to the second loop and cracks the 5 character password in 12 minutes. – Sam Shields Oct 19 '19 at 02:14