0

I'm currently developing a CLI password generator and I've been trying to come up with ways of randomizing characters between a defined set of chars. I know the srand(time(NULL)) method, but as far as I know, it's a bit inconsistent and not so safe to generate random passwords.

I also know there is a way of randomizing numbers using the libsodium library for C (according to this topic), but I'm not sure how to use it. Should I install all the dependencies in my project? It is a relatively small project to have such a huge library. Although I plan on expanding it as time goes by, I don't know if it's worth having a huge library and not use most of its functions.

On top of that, are there specific algorithms to generate passwords other than just randomizing characters? Should I also randomize the array within itself following another algorithm for better consistency, like the Fisher Yates Shuffle?

Jonathan Leffler
760k145 gold badges962 silver badges1.3k bronze badges
asked Apr 6, 2023 at 5:34
1
  • 1
    Using srand() and rand() is probably adequate if RAND_MAX is a lot bigger than 32767 (for example, 2^31 - 1 or 2,147,483,647). Using the time to choose the seed is better than nothing, but it limits the number of random sequences. Consider reading a seed value from /dev/random or something equivalent. I don't think the Fisher-Yates Shuffle is relevant. You simply want to pick a random entry from the array of acceptable characters for each character position in the random password. Commented Apr 6, 2023 at 5:57

1 Answer 1

1

There are lots of issues to resolve, including:

  1. Is the requirement for a function or a program?
  2. If it is a function, can it use the same random number generator (seed) as other parts of the programs it is used in, or should its random numbers be independent of other sequences created by the same program?
  3. How do you create a good random seed?
  4. Assuming you want a function, what should the interface to the function be?
    • For example: extern void gen_random_password(size_t length, char buffer[length]);
    • The length specifies the bytes available in the array.
    • The password will therefore have one character less than the specified length in it, to allow for the null terminator.
  5. Which random number generators are available from your o/s:
    • rand() and srand() will be available 'everywhere'
    • POSIX nrand48() - no hidden seed
    • arc4random() — no seed permitted (BSD, macOS)
    • random() and srandom() (BSD, macOS)
  6. What characters are allowed in a password?

I like using nrand48() because it allows the random password generator to run a series of random numbers independently of any other sequence because it takes the seed — an array of 3 unsigned short integers — as arguments.

Generating a good random seed is tricky. I have code which can be configured to use any of these mechanisms:

  1. Value from reading /dev/random
  2. Value from reading /dev/urandom
  3. Value from arc4random()
  4. Value from mixing clock_gettime() and getpid() and 16-bit CRC
  5. Value from mixing gettimeofday() and getpid() and 16-bit CRC
  6. Value from mixing time() and getpid() and 16-bit CRC

The first two are preferable — there may or may not be a significant difference between /dev/random and /dev/urandom.

Once you've got these important but tedious issues out of the way, the core algorithm for generating a random password is very simple:

grpwd43.h

#ifndef JLSS_ID_GRPWD43_H
#define JLSS_ID_GRPWD43_H
#include <stddef.h>
extern void gen_random_passwd(size_t length, char buffer[length]);
#endif /* JLSS_ID_GRPWD43_H */

grpwd43.c

#include "grpwd43.h" /* SSC: Self-sufficiency check */
#include <assert.h>
#include "randseed.h"
#include "prng48.h"
/*
** Tweak this list of alphanumerics plus punctuation to suit.
** For example, list the alphabet twice (or more) to make letters more
** likely than numbers or punctuation.
*/
static const char password[] =
 "abcdefghijklmnopqrstuvwxyz"
 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
 "0123456789"
 "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
 ;
enum { NUM_PASSWORD = sizeof(password) - 1 };
static int initialized = 0;
void gen_random_passwd(size_t length, char buffer[length])
{
 assert(buffer != NULL && length != 0);
 if (buffer == NULL || length == 0)
 return;
 if (initialized == 0)
 {
 unsigned short seed[3];
 random_seed_bytes(sizeof(seed), seed);
 prng48_seed(seed);
 initialized = 1;
 }
 for (size_t i = 0; i < length - 1; i++)
 {
 buffer[i] = password[prng48_rand(0, NUM_PASSWORD - 1)];
 }
 buffer[length - 1] = '0円';
}
#ifdef TEST
#include <stdio.h>
int main(int argc, char **argv)
{
 for (int i = 11; i < 31; i++)
 {
 char passwd[i];
 gen_random_passwd(i, passwd);
 printf("%d: %s\n", i - 1, passwd);
 }
 return 0;
}
#endif /* TEST */

The other source files needed can be found in my SOQ (Stack Overflow Questions) repository on GitHub in the src/so-7594-6155 sub-directory or in the src/libsoq sub-directory.

answered Apr 24, 2023 at 6:32
Sign up to request clarification or add additional context in comments.

12 Comments

Hello Jonathan! Thank you so much for your response! There are a couple of things I didn't understand though. For example, in your topics 1 and 2, you ask "Is the requirement for a function or a program?", and "should its random numbers be independent of other sequences created by the same program?", how could that be?
Also, this CLI password generator is my final project in CS50, which is still currently under development. You can see it in this repo. I'll be trying to wrap my mind around your reply so I can plug in the concepts you described in your SOQ repository.
If you have a function that will be used repeatedly, the second question (about the independence of random number sequences) becomes important. If you're just writing a program to generate one, or several, random passwords, there is no other random sequence to worry about. If you're doing simulations, you may want to ensure that your random numbers are repeatable. The rand() function has only one seed used by all threads — so independent sequences are not feasible.
The drand48() family of functions provides some which have independent seeds, so you can have different sequences depending on which seed values you use. And the prng48_*() family of functions exploits nrand48() because it takes a user-specified seed. Getting a good, random enough, initial value for the seed is a whole separate bag of worms.
Thank you for your explanation, now I understand it better. I spent the whole day researching the topic and looking at the code you wrote to get a grasp on it. Additionally, I wanted to ask how does drand48() differs from dev/random or dev/urandom? Also, how could I implement the solutions you mentioned in your repository on my project? What libraries would I have to use?
|

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.