Secure Programming Cookbook for C and C++: Recipes for Cryptography, Authentication, Input Validation & More
8.5.1 Problem
You would like to avoid problems with easy-to-guess passwords by randomly generating passwords that are difficult to guess. 8.5.2 Solution
For passwords, choose random characters from an acceptable set of characters using spc_rand_range( ) (see Recipe 11.11). For passphrases, choose random words from a predefined list of acceptable words. 8.5.3 Discussion
In many situations, it may be desirable to present a user with a pregenerated password. For example, if the user is not present at the time of account creation, you will want to generate a reasonably secure password for the account and deliver the password to the user via some secure mechanism such as in person or over the phone. Randomly generated passwords are also useful when you want to enforce safe password requirements. If the user cannot supply an adequately secure password after a certain number of attempts, it may be best to present her with a randomly generated password to use, which will most likely pass all of the requirements tests. The primary disadvantage of randomly generated passwords is that they are usually difficult to memorize (and type), which often results in users writing them down. In many cases, however, this is a reasonable trade-off. The basic strategy for generating a random password is to define a character set that contains all of the characters that are valid for the type of password you are generating, then choose random members of that set until enough characters have been chosen to meet the length requirements. The string spc_password_characters defines the character set from which random password characters are chosen. The function spc_generate_password( ) requires a buffer and the size of the buffer as arguments. The buffer is filled with randomly chosen password characters and is properly NULL-terminated. As written, the function will always succeed, and it will return a pointer to the buffer filled with the randomly generated password. #include <string.h> static char *spc_password_characters = "abcdefghijklmnopqrstuvwxyz0123456789" "ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*( )" "-=_+;[ ]{ }\\|,./<>?;"; char *spc_generate_password(char *buf, size_t bufsz) { size_t choices, i; choices = strlen(spc_password_characters) - 1; for (i = 0; i < bufsz - 1; i++) /* leave room for NULL terminator */ buf[i] = spc_password_characters[spc_rand_range(0, choices)]; buf[bufsz - 1] = 0; return buf; } Although there is no conceptual difference between a password and a passphrase, each has different connotations to users:
While a passphrase can be a long string of random characters and a password can be multiple words, the typical passphrase is a sentence that the user picks, usually because it is related to something that is easily remembered. Even though their length and freeform nature make passphrases much harder to run something such as the Crack program on, they are still subject to guessing. For example, if you are trying to guess someone's passphrase, and you know that person's favorite song, trying some lyrics from that song may prove to be a very good strategy for discovering what the passphrase is. It is important to choose a passphrase carefully. It should be something easy to remember, but it should not be something that someone who knows a little bit about you will be able to guess quickly. As with passwords, there are times when a randomly generated passphrase is needed. The strategy for randomly generating a passphrase is not altogether different from randomly generating a password. Instead of using single characters, whole words are used, separated by spaces. The function spc_generate_passphrase( ) uses a data file to obtain the list of words from which to choose. The words in the file should be ordered one per line, and they should not be related in any way. In addition, the selection of words should be sufficiently large that a brute-force attack on generated passphrases is not feasible. Most Unix systems have a file, /usr/share/dict/words, that contains a large number of words from the English dictionary. This implementation of spc_generate_passphrase( ) keeps the word data file open and builds an in-memory list of the offsets into the file for the beginning of each word. The function keeps offsets instead of the whole words as a memory-saving measure, although with a large enough list of words, the amount of memory required for this list is not insignificant. To choose a word, the function chooses an index into the list of offsets, moves the file pointer to the proper offset, and reads the word. Word lengths can be determined by computing the difference between the next offset and the selected one. #include <stdio.h> #include <stdlib.h> #include <string.h> #define SPC_WORDLIST_FILE "/usr/share/dict/words" static FILE *spc_wordlist_file; static size_t *spc_wordlist_offsets; static size_t spc_wordlist_shortest; static unsigned int spc_wordlist_count; static int load_wordlist(void) { char buf[80]; FILE *f; size_t *offsets, shortest, *tmp; unsigned int count; if (!(f = fopen(SPC_WORDLIST_FILE, "r"))) return 0; if (!(offsets = (size_t *)malloc(sizeof(size_t) * 1024))) { fclose(f); return 0; } count = 0; shortest = ~0; offsets[0] = 0; while (fgets(buf, sizeof(buf), f)) if (buf[strlen(buf) - 1] = = '\n') { if (!((count + 1) % 1024)) { if (!(offsets = (size_t *)realloc((tmp = offsets), sizeof(size_t) * (count + 1025)))) { fclose(f); free(tmp); return 0; } } offsets[++count] = ftell(f); if (offsets[count] - offsets[count - 1] < shortest) shortest = offsets[count] - offsets[count - 1]; } if (!feof(f)) { fclose(f); free(offsets); return 0; } if (ftell(f) - offsets[count - 1] < shortest) shortest = ftell(f) - offsets[count - 1]; spc_wordlist_file = f; spc_wordlist_offsets = offsets; spc_wordlist_count = count; spc_wordlist_shortest = shortest - 1; /* shortest includes NULL terminator */ return 1; } static int get_wordlist_word(unsigned int num, char *buf, size_t bufsz) { size_t end, length; if (num >= spc_wordlist_count) return -1; if (num = = spc_wordlist_count - 1) { fseek(spc_wordlist_file, 0, SEEK_END); end = ftell(spc_wordlist_file); } else end = spc_wordlist_offsets[num + 1]; length = end - spc_wordlist_offsets[num]; /* includes NULL terminator */ if (length > bufsz) return 0; if (fseek(spc_wordlist_file, spc_wordlist_offsets[num], SEEK_SET) = = -1) return -1; fread(buf, length, 1, spc_wordlist_file); buf[length - 1] = 0; return 1; } char *spc_generate_passphrase(char *buf, size_t bufsz) { int attempts = 0, rc; char *outp; size_t left, len; unsigned int idx; if (!spc_wordlist_file && !load_wordlist( )) return 0; outp = buf; left = bufsz - 1; while (left > spc_wordlist_shortest) { idx = spc_rand_range(0, spc_wordlist_count - 1); rc = get_wordlist_word(idx, outp, left + 1); if (rc = = -1) return 0; else if (!rc && ++attempts < 10) continue; else if (!rc) break; len = strlen(outp) + 1; *(outp + len - 1) = ' '; outp += len; left -= len; } *(outp - 1) = 0; return buf; } When spc_generate_passphrase( ) is called, it opens the data file containing the words to choose from and leaves it open. In addition, depending on the size of the file, it may allocate a sizable amount of memory that remains allocated. When you're done generating passphrases, you should call spc_generate_cleanup( ) to close the data file and free the memory allocated by spc_generate_passphrase( ). void spc_generate_cleanup(void) { if (spc_wordlist_file) fclose(spc_wordlist_file); if (spc_wordlist_offsets) free(spc_wordlist_offsets); spc_wordlist_file = 0; spc_wordlist_offsets = 0; spc_wordlist_count = 0; spc_wordlist_shortest = 0; } 8.5.4 See Also
Recipe 11.11 |