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 incosistent 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? Is a relative small project to have such a huge library. Although I plan 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? Thanks in advance!

thevoyager
  • 33
  • 5
  • 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. – Jonathan Leffler Apr 06 '23 at 05:57

1 Answers1

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.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • 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? – thevoyager May 04 '23 at 16:57
  • Also, this CLI password generator is my final project in CS50, which is still currently under development. You can see it in this [repo](https://github.com/lknknm/hipass-pass-gen). I'll be trying to wrap my mind around your reply so I can plug in the concepts you described in your SOQ repository. – thevoyager May 04 '23 at 17:03
  • 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. – Jonathan Leffler May 04 '23 at 23:06
  • 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. – Jonathan Leffler May 04 '23 at 23:09
  • 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? – thevoyager May 04 '23 at 23:24
  • The devices attempt to be — and very largely succeed at being — unrepeatable. They are effectively true random number generators (TRNGs) rather than pseudo-random number generators (PRNGs). OTOH, `drand48()` et al are strictly PRNGs — given the same seed, they will produce the same sequence of pseudo-random numbers. One factor that affects the quality of a PRNG is how much state it keeps in the seed. The `rand()` function typically uses a 32-bit seed; `drand48()` et al use 48-bits (hence the moniker). You can find things like the Mersenne Twister and Knuth's random number generator too. – Jonathan Leffler May 04 '23 at 23:47
  • So is it better just using `dev/random` since it's a TRNG? – thevoyager May 05 '23 at 00:17
  • It depends on your requirements, but yes, you certainly could read `/dev/random` and use that for every random byte of the password. OTOH, if there might be a need to regenerate a password, a PRNG is a better choice. In theory, `/dev/random` might block because it doesn't have enough entropy — `/dev/urandom` has a fallback scheme (but on macOS, the two devices are equivalent) and non-blocking). Most likely, you won't have problems with lack of entropy, but it is nominally something to worry about. And it's quite plausible that you won't need to be able to regenerate the random passwords. – Jonathan Leffler May 05 '23 at 04:40
  • So yeah, I was doing research and `dev/urandom` is better than `dev/random` because the lack of entropy for `random` can be somewhat problematic, even though I don't want to build a function to regenerate the passwords for now. I'm curious though how your implementation of `randseed()` uses the `dev/urandom` and how it actually differs from `drand48()`. I don't have much knowledge on this subject. – thevoyager May 05 '23 at 14:19
  • The choice to use `/dev/random` is made when the `randseed.c` file is compiled — and can be adjusted by `-Dxyz` configuration parameters. The header yields a platform-independent interface, so consumers of the code don't have to worry about it — they get a decent random number that isn't just the time of day to use as a seed. This mechanism is completely independent of the `drand48()` family of functions; the random seed code provides a seed to the PRNG and the PRNG then generates its random numbers. Because it uses `nrand48()`, it doesn't affect other code also using these PRNG functions. – Jonathan Leffler May 05 '23 at 14:28
  • This makes it much clearer. So `randseed.c` uses `dev/urandom` or `dev/random` to "feed" a seed to `drand48()` so it can generate its own random numbers based on the devices. Is it possible that I can use your family of functions you wrote as a library for my project? What credits must be given? – thevoyager May 05 '23 at 14:55
  • Please contact me by email — see my profile, and include Stack Overflow in the subject line, preferably with the question number (SO 7594-6155). Remember what it says at the bottom of the page: "user contributions licensed under [CC BY-SA](https://stackoverflow.com/help/licensing)." – Jonathan Leffler May 05 '23 at 16:07