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?
1 Answer 1
There are lots of issues to resolve, including:
- Is the requirement for a function or a program?
- 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?
- How do you create a good random seed?
- 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.
- For example:
- Which random number generators are available from your o/s:
rand()andsrand()will be available 'everywhere'- POSIX
nrand48()- no hidden seed arc4random()— no seed permitted (BSD, macOS)random()andsrandom()(BSD, macOS)
- 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:
- Value from reading /dev/random
- Value from reading /dev/urandom
- Value from arc4random()
- Value from mixing clock_gettime() and getpid() and 16-bit CRC
- Value from mixing gettimeofday() and getpid() and 16-bit CRC
- 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.
12 Comments
rand() function has only one seed used by all threads — so independent sequences are not feasible.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.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?Explore related questions
See similar questions with these tags.
srand()andrand()is probably adequate ifRAND_MAXis a lot bigger than32767(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/randomor 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.