3
\$\begingroup\$

I decided to write a console program that can generate a random alphanumerical password in the C language. It's quite useful for when I'm making a new account and need to make up a quick password on the spot. I've noticed a lot of the password generators people have shown on this are written in Java and maybe Python or C# so hopefully mine will add some variety.

This is my code, I've aimed to make it as simple as possible:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>

int main()
{
    srand((unsigned int)(time(NULL)));
    int i;
    char pass[12];

    printf("Press enter to get a twelve-character password\n");
    getchar();

    for (i = 0; i < 4; i++) {
        pass[i] = rand() % 9;
        char capLetter = 'A' + (rand() % 26);
        pass[i + 2] = capLetter;
        char letter = 'a' + (rand() % 26);
        pass[i + 3] = letter;
        printf("%d%c%c", pass[i], pass[i + 2], pass[i + 3]);
    }
    printf("\n\n");
}

I can generate decent passwords, such as: 7Qb4Le2Id0Ss, 1Sw0Nb2Ky1Zp, 0Am3Wa4Wo1Tm and 4Rr4My1Mt1Gj. Like I said, I want to make it more efficient if I can, and perhaps add some punctuation, which does not seem that easy, since the ASCII codes are quite spread out.

Does anyone have any ideas?

\$\endgroup\$
  • \$\begingroup\$ How many passwords are you trying to generate? \$\endgroup\$ – Dair Aug 14 '16 at 20:49
  • 3
    \$\begingroup\$ I don't know if this is intentional, but you're only generating digits in the range 0 to 8. You need to use rand()%10 for digits in the range 0 to 9. \$\endgroup\$ – kamoroso94 Aug 15 '16 at 15:14
  • \$\begingroup\$ Oh I didn't realise that, it was supposed to go from 0 to 9, I was thinking about 1 to 9, that's why I put rand() % 9. Thanks. \$\endgroup\$ – AkThao Aug 16 '16 at 10:33
  • \$\begingroup\$ Please do not post edits invalidating existing answers. \$\endgroup\$ – Hosch250 Aug 17 '16 at 17:16
  • \$\begingroup\$ BTW. your "random" password only depends on the current time (same time, same password) and you can only have 2^32 different passwords. Look at stackoverflow.com/questions/44222866/… \$\endgroup\$ – domenukk Jul 26 at 14:55
3
\$\begingroup\$

Right now, your password generator can only generate (9×26×26)4 (about 1.3×1015) possible passwords. However, we can increase this to 9412 (about 4.7×1023) possible passwords by including symbols, digits, and alphabetic characters. In ASCII, all of these non-whitespace printable characters are in the range 33 to 126.

ASCII Table

To accomplish this, you can modify part of your code like so to use 12 random characters in this range.

for(i = 0; i < 12; i++) {
    pass[i] = 33 + rand() % 94;
}
pass[i] = '\0';
printf("%s\n",pass);
\$\endgroup\$
  • \$\begingroup\$ OP's combination count is limited to UINT_MAX + 1 as code only used that many different initialization states. \$\endgroup\$ – chux Aug 16 '16 at 12:14
  • \$\begingroup\$ Are you referring to the limits of the rand function? Either way, increasing the alphabet size from 62 to 94 is still an improvement. \$\endgroup\$ – kamoroso94 Aug 16 '16 at 13:35
  • \$\begingroup\$ increasing the alphabet size does not increase the number of different passwords generated. srand() only takes UINT_MAX + 1 (typically 4,294,967,296) different values. OP's code will generate only 4 billion different passwords. In a given hour, it will only generate 3,600 different passwords, at best, once each second. \$\endgroup\$ – chux Aug 16 '16 at 14:58
  • \$\begingroup\$ I see what you mean, you never really benefit from the complete range of passwords that can be possibly generated in theory, but if you tried to brute force a password that followed the pattern I provided versus the one the OP provided, I believe mine would stand a better chance. \$\endgroup\$ – kamoroso94 Aug 16 '16 at 15:03
  • \$\begingroup\$ OK, try srand((unsigned int)(time(NULL))); ... for(i = 0; i < 12; i++) { pass[i] = 33 + rand() % 94; } pass[i] = '\0'; printf("%s\n",pass); and tell me the first 3 characters (pretend someone watched you type in 12 characters and remembered the first 3), see if I can find the last 9. The point being, if we use the same rand(), it should be trivial. \$\endgroup\$ – chux Aug 16 '16 at 15:06
3
\$\begingroup\$

Though you are getting desired output, here in your code you are not exactly generating characters at all indices/positions of the char pass[12]; array. let me explain

consider the following for loop in your code:

for (i = 0; i < 4; i++) {
    pass[i] = rand() % 10; //10 instead of 9 to produce digits from 0 to 9
    char capLetter = 'A' + (rand() % 26);
    pass[i + 2] = capLetter;
    char letter = 'a' + (rand() % 26);
    pass[i + 3] = letter;
    printf("%d%c%c", pass[i], pass[i + 2], pass[i + 3]);
}

consider the maximum cases i.e, when i = 3 and pass[i + 3], then pass[i + 3] is equivalent to pass[6] so the maximum index value that you can access is 6. So you are not generating random value at all indices of the pass[] array but only within 0 to 6 positions and printing the generated characters in each iteration.

Instead try using for loop this way to generate random character at all the indices:

for (i = 0; i < 4; i++) 
{
    pass[ 3 * i ] = rand() % 10;
    char capLetter = 'A' + (rand() % 26);
    pass[(3 * i) + 1] = capLetter;
    char letter = 'a' + (rand() % 26);
    pass[(3 * i) + 2] = letter;
    printf("%d%c%c", pass[3 * i], pass[(3 * i) + 1], pass[(3 * i) + 2]);
}

here you can access all the 12 indices i.e, 0 to 11

further, instead of using pass[i] = rand() % 9; to generate a random character and using %d, you can do pass[i] = '0' + (rand() % 10); (use 10 instead of 9 if not 9 will never be printed) to generate a numeric character and use %c. Since you have a character array, you can append the array with a null terminating character ('\0') and make it a string and print the generated password this way:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>   //not required    
#include <time.h>

int main(void) //int main() is not a valid signature in C
{
    srand((unsigned int)(time(NULL)));
    int i;
    char pass[13]; //extra byte for null terminating character

    printf("Press enter to get a twelve-character password\n");
    getchar();

    for (i = 0; i < 4; i++) 
    {
        //revised logic to generate random characters at all positions (0 - 11)
        pass[ 3 * i ] = '0' + (rand() % 10); //generating numeric character
        char capLetter = 'A' + (rand() % 26); //generating upper case alpha character
        pass[(3 * i) + 1] = capLetter;
        char letter = 'a' + (rand() % 26); //generating lower case alpha character
        pass[(3 * i) + 2] = letter;
    }
    pass[3 * i] = '\0'; //placing null terminating character at the end
    printf("generated password : %s\n\n",pass); //printing the string

    printf("\n\n");
}

I want to make it more efficient if I can, and perhaps add some punctuation, which does not seem that easy, since the ASCII codes are quite spread out.

Here is a way to generate passwords which includes punctuation without caring about ASCII code at all! Right now, you are following a fixed pattern of printing uppercase letter followed by number and then a lowercase letter, and you repeat it for three more times to generate a 12 character password. Instead you could randomly generate the password without any such pattern and not using ASCII codes at all!

  1. create a string of all permissible characters
  2. use the string to print 12 character from any 12 random positions

here's the program which follows the above two steps to generate a password and you can add all the permissible characters in the string which I have not mentioned

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void) 
{
     srand((unsigned int)(time(NULL)));

    int index = 0;

    //step 1
    char characters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/,.-+=~`<>:";
    //I just added a few punctuations characters for explanatory purpose
    //you can add all the additional punctuations which are required

    //step 2
    for(index = 0; index < 12; index++)
    {
        printf("%c", characters[rand() % (sizeof characters - 1)]);
    }

}

I'd say generating the password is much more advantageous as you can only randomly select from the characters that provide in the string. All you need to do is just put in all the permissible characters in the string and you are bound to get a randomly generated password :)

\$\endgroup\$
  • 1
    \$\begingroup\$ @Cherubim Anand either add () like '0' + (rand() % 9) or remove them like 'A' + rand() % 26. Looking for consistency. \$\endgroup\$ – chux Aug 16 '16 at 2:22
  • 1
    \$\begingroup\$ These improvements are much appreciated, the code actually looks much better now. I now realise the difference in the for loop from my original one. From what I understand, in my original loop, I was only creating the first three characters and replacing them each time. As for the terminating character, pass[3 * i] = '\0'; is this when i = 4? Because I thought i finishes at 3 because it's i < 4, not i <= 4. \$\endgroup\$ – AkThao Aug 16 '16 at 10:44
  • \$\begingroup\$ Yes @kashveyron it's when i = 4, i doesn't finish at 3 but the loop continues till the condition i<4 is evaluated to be false. when i becomes 4 as i<4 is false you exit the loop.. therefore YES pass[3 * i] = '\0'; is when i = 4 \$\endgroup\$ – Cherubim Aug 16 '16 at 12:48
  • \$\begingroup\$ @CherubimAnand Thank you for clearing up the confusion about i being equal to 4, not 3. As for the pattern, preferably it should be completely random, like you have put in your answer. I only did it in the original pattern because that was the first idea that came to my head when writing the program. Nevertheless, your suggestion is a better to do it. \$\endgroup\$ – AkThao Aug 16 '16 at 18:49
  • \$\begingroup\$ @CherubimAnand Funnily enough, I was actually just thinking about that a while ago and I just figured out how to do it now :) I've given up votes (although they are not shown publicly because I do not have enough reputation yet) and I have marked all three answers as acceptable, as all the answers are brilliant and have been helpful. I have now appended my code based on everyone's answers and have posted the new version under 'EDIT:' in my opening question, feel free to check it out. \$\endgroup\$ – AkThao Aug 17 '16 at 16:55
1
\$\begingroup\$

Punctuation

I want to make it more efficient if I can, and perhaps add some punctuation, which does not seem that easy, since the ASCII codes are quite spread out

To use punctuation, simply select from an array.

static const char punct_set[] = "~!@#$%^&*()_+";
char punct = punct_set[rand() % (sizeof punct_set - 1)];

Pedantic: Using this method with letters would make code more portable to more machines as 'a' to 'z' may not be consecutive.

static const char lower_az[] = "abcdefghijklmnopqrstuvwxyz";
char lower_letter = lower_az[rand() % (sizeof lower_az - 1)];

Clean-up

When using passwords, good to scrub buffers: a small step to avoid memory dumps harvesting information.

volatile char pass[12];
...
    printf("%d%c%c", pass[i], pass[i + 2], pass[i + 3]);
}
// Add
memset(pass, 0, sizeof pass);

Initialization

srand((unsigned int)(time(NULL))); is weak, as if one knows the code and about the time used, others can guess the password generated to within a few hundred/thousand combinations. Consider other sources of randomness. Some ideas:

clock_t start = clock();
printf("Press enter to get a twelve-character password\n");
getchar();
unsigned int delta = (unsigned int) (clock() - start);

unsigned int time = (unsigned int) time(NULL);

unsigned int pid = (unsigned int) getpid(void);

srand(delta ^ time ^ pid);  // ^ is OK and simple, other better mixing methods exist.

// or *NIX
int urandom_fd = -2;
urandom_fd = open("/dev/urandom", O_RDONLY);
unsigned int init;
read(urandom_fd, &init, sizeof init);
srand(init);
close(urandom_fd);

Even this is not so great as your code will only generate about UINT_MAX different passwords instead of pow(10*26*26,4). This is a deep subject, so only starting with some basic ideas such as using a new random initial value for each triple.

printf("Press enter to get a twelve-character password\n");
getchar();

srand(some_random_source);
// Get first triple digit, LETTER, letter

srand(some_uncorrelated_random_source);
// Get next triple digit, LETTER, letter

srand(some_uncorrelated_random_source);
// Get next triple digit, LETTER, letter

srand(some_uncorrelated_random_source);
// Get last triple digit, LETTER, letter

Or far better, use a random number generator with a far longer period and initialization state. Example Mersenne Twister

\$\endgroup\$
  • 1
    \$\begingroup\$ @Cherubim Anand The size of char punct_set[] = "~!@" is 4 - remember the null character.. Still maintain sizeof punct_set - 1 is the correct code. \$\endgroup\$ – chux Aug 16 '16 at 2:20
  • \$\begingroup\$ I understand the punctuation section and actually find it a good idea. But I don't really understand the clean-up and initialisation sections. I mean, I kind of understand what you're talking about, but I don't get most of the code. I assume it's about using a few different functions to make the password more random and harder to crack. \$\endgroup\$ – AkThao Aug 16 '16 at 12:16
  • \$\begingroup\$ @kashveyron clean-up: After your program is run, the OS may not do anything special with memory. Should a nefarious program run that can access select memory, the password generated could still be there. Generating a random password for you is not useful if others can see what you were given. Hence suggest cleaning up buffers. \$\endgroup\$ – chux Aug 16 '16 at 14:53
  • \$\begingroup\$ Thank you, that makes sense to me now, so I assume the memset function overwrites pass with zeros. What does volatile mean before char? \$\endgroup\$ – AkThao Aug 16 '16 at 18:54
  • \$\begingroup\$ @kashveyron volatile tells the compiler the value may change outside programs control and to not optimize access. This may help in this case as an optimizing compile may otherwise see a non-volatile pass is not used after memset(pass, 0, sizeof pass); and possible optimizing the function call away. \$\endgroup\$ – chux Aug 16 '16 at 19:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.