--- pgp/src/random.c 2018/04/24 16:42:07 1.1.1.7 +++ pgp/src/random.c 2018/04/24 16:44:31 1.1.1.8 @@ -38,89 +38,196 @@ #include "pgp.h" /* For globalRandseedName */ #include "randpool.h" +/* + * As of PGP 2.6.2, the randseed.bin file has been expanded. An explanation + * of how the whole thing works in in order, as people are always suspiscious + * of the random number generator. (After the xorbytes bug in 2.6, perhaps + * you should be.) There are two random number generators in PGP. One + * is the "cryptRand" family, which is based on X9.17, but uses IDEA instead + * of 2-key EDE triple-DES. This is the generator with a lot of peer review. + * The implementation is in idea.c. + * The second is the "trueRand" family, which attempt to accurately measure + * the entropy available from keyboard I/O. It keeps a lot more state. + * The implementation of this is in randpool.c. + * Originally, the trueRand generator was only used for generating primes, + * and the cryptRand for generating IDEA session keys. But things have + * become a bit more complex. In particular, the X9.17 specification + * wants a source of high-resolution time-of-day information, as a source + * of some "true" randomness to throw in. So we use the trueRand pool + * for that. + * The cryptRand functions keep a state file around, usually named + * randseed.bin, for a seed, as the X9.17 generator requires 24 bytes of + * known initial information. + * This data in this file is carefully "washed" before and after use to + * help ensure that if the file is captured or altered, the keys will + * not be too vulnerable. A washing consists of an encryption in PGP's + * usual CFB mode of the material coming from or being written to the + * randseed.bin file on disk. Assuming the cipher is strong, the effects + * of the wash are as difficult to predict as the key that is used is + * difficult to guess. + * Beforehand, we use the MD5 of the file being encrypted as an additional + * source of randomness (on the theory that an attacker trying to break + * a session key probably doesn't have the plaintext, or he wouldn't need + * to bother), and use that as an IDEA key (with a fixed IV of zero) + * to encrypt the randseed.bin data. + * After generating an IDEA key and IV, some more random bytes are generated + * to reinitialize randseed.bin, and these are encrypted in the same manner + * as the PGP message before being written to disk, on the assumption that + * if an attacker can decrypt that, they can decrypt the message directly + * and not bother attacking the randseed.bin file. + * The previous code only saved the 24 bytes needed by the X9.17 algorithm. + * But in 2.6.2, we decided to make the randseed.bin file substantially + * larger to hold more information that a would-be attacker must guess. + * There are two reasons for this: + * - Every time you run PGP, especially when responding to one of PGP's + * prompts, PGP samples the keystrokes for use as random numbers. + * It is a shame to throw this entropy (randomness) away just because + * there is no need for it in the current invocation of PGP. + * - A feature was added to 2.6.2 to generate files full of random bytes + * for other programs to use as key material. In this case, we haven't + * got a message we're encrypting to take some entropy from, and we may + * be asked to generate more than 24 random bytes, so there should be + * more than 24 bytes of seed material to work from. + * The implementation is added on to the previous one, to offer assurance + * that it is no weaker. + * When the cryptRand generator is opened, the file is washed (if possible) + * and the first 24 bytes are fed to the cryptographic RNG, while the + * remainder is added to the trueRand random number pool. + * When saving, the randseed.bin file is refilled with newly generated + * bytes, again washed if possible. It turns out (if you study the + * X9.17 RNG) that each of the 2^64 possible timestamp information + * values used in generating each 8 bytes of output generates a output + * value, so the entropy in the trueRand pool is put to good use; this + * is not just generating more data from 24 bytes of seed. + * The random pool is opened and saved with a washing key when + * generating a session key (see make_random_ideakey in crypto.c), + * but it is also opened (harmless if alreasy open) and saved + * (harmless if already saved) without a washing key in the exitPGP routine, + * to mix in any entropy collected in this invocation of PGP even if + * a session key was not generated. + */ + +/* + * The new randseed size, big enough to hold the full context of the cryptRand + * and trueRand generators. With the current RANDPOOLBITS of 3072 (384 bytes), + * that's 408 bytes. It's useless to make it any larger, although if + * RANDPOOLBITS is increased, it might be an idea to keep this smaller than + * one disk block on all systems (512 bytes is a good figure to use) + * so we don't change the space requirements for randseed.bin. + */ +#define RANDSEED_BYTES (RANDPOOLBITS/8 + 24) +/* Have we read in the randseed.bin file? */ +static boolean randSeedOpen = 0; static struct IdeaRandContext randContext; -static int randInitFlag = 0; /* * Load the RNG state from the randseed.bin file on disk. * Returns 0 on success, <0 on error. + * + * If cfb is non-zero, prewashes the data by encrypting with it. */ int -cryptRandOpen(void) +cryptRandOpen(struct IdeaCfbContext *cfb) { - byte buf[24]; + byte buf[256]; int len; FILE *f; - if (randInitFlag) - return 0; + if (randSeedOpen) + return 0; /* Already open */ f = fopen(globalRandseedName, FOPRBIN); if (!f) return -1; + + /* First get the bare minimum 24 bytes we need for the IDEA RNG */ len = fread((char *)buf, 1, 24, f); - fclose(f); + if (cfb) + ideaCfbEncrypt(cfb, buf, buf, 24); ideaRandInit(&randContext, buf, buf+16); - randInitFlag = 1; + randSeedOpen = TRUE; + if (len != 24) { /* Error */ + fclose(f); + return -1; + } + + /* Read any extra into the random pool */ + for (;;) { + len = fread((char *)buf, 1, sizeof(buf), f); + if (len <= 0) + break; + if (cfb) + ideaCfbEncrypt(cfb, buf, buf, len); + randPoolAddBytes(buf, len); + } - return (len == 24) ? 0 : -1; + fclose(f); + burn(buf); + return 0; } +/* Create a new state from the output of trueRandByte */ void -cryptRandCreate(void) +cryptRandInit(struct IdeaCfbContext *cfb) { - byte randbuf[24]; + byte buf[24]; int i; - FILE *f; - for (i = 0; i < 24; i++) - randbuf[i] = trueRandByte(); + for (i = 0; i < sizeof(buf); i++) + buf[i] = trueRandByte(); + if (cfb) + ideaCfbEncrypt(cfb, buf, buf, sizeof(buf)); - ideaRandInit(&randContext, (byte *)randbuf, (byte *)randbuf+16); - randInitFlag = 1; + ideaRandInit(&randContext, buf, buf+16); + randSeedOpen = TRUE; + burn(buf); +} - f = fopen(globalRandseedName, FOPWBIN); - if (f) { - fwrite(randbuf, 1, 24, f); - fclose(f); - } - return; +byte +cryptRandByte(void) +{ + if (!randSeedOpen) + cryptRandOpen((struct IdeaCfbContext *)0); + return ideaRandByte(&randContext); } /* - * Encrypt the state of the PRNG with the given key. It is intended - * that this key is the md5 of the message to be encrypted, which is - * unpredictable to a would-be attacker who does not posess the message. - * This is simply a way to get some "random" bytes without a random - * number source. This "prewash" attempts to reduce the value of a - * captured randseed.bin file. + * Write out a file of random bytes. If cfb is defined, wash it with the + * cipher. */ -void -cryptRandWash(byte const key[16]) +int +cryptRandWriteFile(char const *name, struct IdeaCfbContext *cfb, unsigned bytes) { - struct IdeaCfbContext cfb; + byte buf[256]; + FILE *f; + int i, len; - if (!randInitFlag) - cryptRandOpen(); - ideaCfbInit(&cfb, key); - ideaRandWash(&randContext, &cfb); - ideaCfbDestroy(&cfb); -} + f = fopen(name, FOPWBIN); + if (!f) + return -1; -byte -cryptRandByte(void) -{ - if (!randInitFlag) - cryptRandOpen(); - return ideaRandByte(&randContext); + while (bytes) { + len = (bytes < sizeof(buf)) ? bytes : sizeof(buf); + for (i = 0; i < len; i++) + buf[i] = ideaRandByte(&randContext); + if (cfb) + ideaCfbEncrypt(cfb, buf, buf, len); + i = fwrite(buf, 1, len, f); + if (i < len) + break; + bytes -= len; + } + + return (fclose(f) != 0 || bytes != 0) ? -1 : 0; } /* - * Create a new RNG state, encrypt it with the session key, and save it - * out to disk. The RNG is re-initialized with the saved parameters. + * Create a new RNG state, encrypt it with the supplied key, and save it + * out to disk. * - * This uses the same key and initial vector (including the repeated - * check bytes and everything) that is used to encrypt the user's message. + * When we encrypt a file, the saved data is "postwashed" using the + * same key and initial vector (including the repeated check bytes and + * everything) that is used to encrypt the user's message. * The hope is that this "postwash" renders it is at least as hard to * derive old session keys from randseed.bin as it is to crack the the * message directly. @@ -128,34 +235,25 @@ cryptRandByte(void) * The purpose of using EXACTLY the same encryption is to make sure that * there isn't related, but different data floating around that can be * used for cryptanalysis. + * + * This function is always called by exitPGP, without a washing encryption, + * so this function ignores that call if it has previously been called + * to save washed bytes. */ void -cryptRandSave(byte const key[16], byte const iv[8]) +cryptRandSave(struct IdeaCfbContext *cfb) { - struct IdeaCfbContext cfb; - byte buf[24]; - FILE *f; - int i; + static boolean savedwashed = FALSE; - /* This IV is EXACTLY the same as is used on bulk files. */ - memcpy(buf, iv, 8); - buf[8] = buf[6]; /* "check bytes" */ - buf[9] = buf[7]; - - ideaCfbInit(&cfb, key); - ideaCfbEncrypt(&cfb, buf, buf, 10); - ideaCfbSync(&cfb); - for (i = 0; i < 24; i++) - buf[i] = ideaRandByte(&randContext); - ideaCfbEncrypt(&cfb, buf, buf, 24); - ideaCfbDestroy(&cfb); - ideaRandInit(&randContext, buf, buf+16); + if (!randSeedOpen) + return; /* Do nothing */ - f = fopen(globalRandseedName, FOPWBIN); - if (f) { - fwrite(buf, 1, 24, f); - fclose(f); - } + if (cfb) + savedwashed = TRUE; + else if (savedwashed) + return; /* Don't re-save if it's already been saved washed. */ + + (void)cryptRandWriteFile(globalRandseedName, cfb, RANDSEED_BYTES); } /* @@ -427,6 +525,8 @@ getstring(char *strbuf, unsigned maxlen, putch(BS); } i--; + } else { + putch('\007'); } continue; }