Merge pull request #341 from libtom/review/prngs

Review of prngs
This commit is contained in:
Steffen Jaeckel 2018-03-23 22:00:56 +01:00 committed by GitHub
commit 8a6ee82e17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 136 additions and 172 deletions

View File

@ -3822,11 +3822,15 @@ key, and any hash that produces at least a 256--bit output. However, to make th
it has been fixed to those choices.
Fortuna is more secure than Yarrow in the sense that attackers who learn parts of the entropy being
added to the PRNG learn far less about the state than that of Yarrow. Without getting into to many
added to the PRNG learn far less about the state than that of Yarrow. Without getting into too many
details Fortuna has the ability to recover from state determination attacks where the attacker starts
to learn information from the PRNGs output about the internal state. Yarrow on the other hand, cannot
recover from that problem until new entropy is added to the pool and put to use through the ready() function.
For detailed information on how the algorithm works and what you have to do to maintain the secure state
get a copy of the book\footnote{Niels Ferguson and Bruce Schneier, Practical Cryptography. ISBN 0-471-22357-3.} or
read the paper online\footnote{\url{https://www.schneier.com/academic/paperfiles/fortuna.pdf} [Accessed on 7th Dec. 2017]}.
\subsubsection{RC4}
RC4 is an old stream cipher that can also double duty as a PRNG in a pinch. You key RC4 by

View File

@ -212,6 +212,31 @@ int register_all_prngs(void);
int prng_is_valid(int idx);
LTC_MUTEX_PROTO(ltc_prng_mutex)
#ifdef LTC_SOURCE
/* internal helper functions */
#define _LTC_PRNG_EXPORT(which) \
int which ## _export(unsigned char *out, unsigned long *outlen, prng_state *prng) \
{ \
unsigned long len = which ## _desc.export_size; \
\
LTC_ARGCHK(prng != NULL); \
LTC_ARGCHK(out != NULL); \
LTC_ARGCHK(outlen != NULL); \
\
if (*outlen < len) { \
*outlen = len; \
return CRYPT_BUFFER_OVERFLOW; \
} \
\
if (which ## _read(out, len, prng) != len) { \
return CRYPT_ERROR_READPRNG; \
} \
\
*outlen = len; \
return CRYPT_OK; \
}
#endif
/* Slow RNG you **might** be able to use to seed a PRNG with. Be careful as this
* might not work on all platforms as planned
*/

View File

@ -150,26 +150,7 @@ int chacha20_prng_done(prng_state *prng)
@param prng The PRNG to export
@return CRYPT_OK if successful
*/
int chacha20_prng_export(unsigned char *out, unsigned long *outlen, prng_state *prng)
{
unsigned long len = chacha20_prng_desc.export_size;
LTC_ARGCHK(prng != NULL);
LTC_ARGCHK(out != NULL);
LTC_ARGCHK(outlen != NULL);
if (*outlen < len) {
*outlen = len;
return CRYPT_BUFFER_OVERFLOW;
}
if (chacha20_prng_read(out, len, prng) != len) {
return CRYPT_ERROR_READPRNG;
}
*outlen = len;
return CRYPT_OK;
}
_LTC_PRNG_EXPORT(chacha20_prng)
/**
Import a PRNG state

View File

@ -37,7 +37,7 @@ we reseed automatically when len(pool0) >= 64 or every LTC_FORTUNA_WD calls to t
const struct ltc_prng_descriptor fortuna_desc = {
"fortuna",
(32 * LTC_FORTUNA_POOLS), /* default: 1024 */
64,
&fortuna_start,
&fortuna_add_entropy,
&fortuna_ready,
@ -66,9 +66,9 @@ static int _fortuna_reseed(prng_state *prng)
{
unsigned char tmp[MAXBLOCKSIZE];
hash_state md;
ulong64 reset_cnt;
int err, x;
++prng->fortuna.reset_cnt;
/* new K == LTC_SHA256(K || s) where s == LTC_SHA256(P0) || LTC_SHA256(P1) ... */
sha256_init(&md);
@ -77,8 +77,10 @@ static int _fortuna_reseed(prng_state *prng)
return err;
}
reset_cnt = prng->fortuna.reset_cnt + 1;
for (x = 0; x < LTC_FORTUNA_POOLS; x++) {
if (x == 0 || ((prng->fortuna.reset_cnt >> (x-1)) & 1) == 0) {
if (x == 0 || ((reset_cnt >> (x-1)) & 1) == 0) {
/* terminate this hash */
if ((err = sha256_done(&prng->fortuna.pool[x], tmp)) != CRYPT_OK) {
sha256_done(&md, tmp);
@ -108,9 +110,10 @@ static int _fortuna_reseed(prng_state *prng)
}
_fortuna_update_iv(prng);
/* reset pool len */
/* reset/update internals */
prng->fortuna.pool0_len = 0;
prng->fortuna.wd = 0;
prng->fortuna.reset_cnt = reset_cnt;
#ifdef LTC_CLEAN_STACK
@ -121,6 +124,46 @@ static int _fortuna_reseed(prng_state *prng)
return CRYPT_OK;
}
/**
"Update Seed File"-compliant update of K
@param in The PRNG state
@param inlen Size of the state
@param prng The PRNG to import
@return CRYPT_OK if successful
*/
static int _fortuna_update_seed(const unsigned char *in, unsigned long inlen, prng_state *prng)
{
int err;
unsigned char tmp[MAXBLOCKSIZE];
hash_state md;
LTC_MUTEX_LOCK(&prng->lock);
/* new K = LTC_SHA256(K || in) */
sha256_init(&md);
if ((err = sha256_process(&md, prng->fortuna.K, 32)) != CRYPT_OK) {
sha256_done(&md, tmp);
goto LBL_UNLOCK;
}
if ((err = sha256_process(&md, in, inlen)) != CRYPT_OK) {
sha256_done(&md, tmp);
goto LBL_UNLOCK;
}
/* finish key */
if ((err = sha256_done(&md, prng->fortuna.K)) != CRYPT_OK) {
goto LBL_UNLOCK;
}
_fortuna_update_iv(prng);
LBL_UNLOCK:
LTC_MUTEX_UNLOCK(&prng->lock);
#ifdef LTC_CLEAN_STACK
zeromem(&md, sizeof(md));
#endif
return err;
}
/**
Start the PRNG
@param prng [out] The PRNG state to initialize
@ -251,6 +294,11 @@ unsigned long fortuna_read(unsigned char *out, unsigned long outlen, prng_state
}
}
/* ensure that one reseed happened before allowing to read */
if (prng->fortuna.reset_cnt == 0) {
goto LBL_UNLOCK;
}
/* now generate the blocks required */
tlen = outlen;
@ -329,71 +377,7 @@ LBL_UNLOCK:
@param prng The PRNG to export
@return CRYPT_OK if successful
*/
int fortuna_export(unsigned char *out, unsigned long *outlen, prng_state *prng)
{
int x, err;
hash_state *md;
unsigned long len = fortuna_desc.export_size;
LTC_ARGCHK(out != NULL);
LTC_ARGCHK(outlen != NULL);
LTC_ARGCHK(prng != NULL);
LTC_MUTEX_LOCK(&prng->lock);
if (!prng->ready) {
err = CRYPT_ERROR;
goto LBL_UNLOCK;
}
/* we'll write bytes for s&g's */
if (*outlen < len) {
*outlen = len;
err = CRYPT_BUFFER_OVERFLOW;
goto LBL_UNLOCK;
}
md = XMALLOC(sizeof(hash_state));
if (md == NULL) {
err = CRYPT_MEM;
goto LBL_UNLOCK;
}
/* to emit the state we copy each pool, terminate it then hash it again so
* an attacker who sees the state can't determine the current state of the PRNG
*/
for (x = 0; x < LTC_FORTUNA_POOLS; x++) {
/* copy the PRNG */
XMEMCPY(md, &(prng->fortuna.pool[x]), sizeof(*md));
/* terminate it */
if ((err = sha256_done(md, out+x*32)) != CRYPT_OK) {
goto LBL_ERR;
}
/* now hash it */
if ((err = sha256_init(md)) != CRYPT_OK) {
goto LBL_ERR;
}
if ((err = sha256_process(md, out+x*32, 32)) != CRYPT_OK) {
goto LBL_ERR;
}
if ((err = sha256_done(md, out+x*32)) != CRYPT_OK) {
goto LBL_ERR;
}
}
*outlen = len;
err = CRYPT_OK;
LBL_ERR:
#ifdef LTC_CLEAN_STACK
zeromem(md, sizeof(*md));
#endif
XFREE(md);
LBL_UNLOCK:
LTC_MUTEX_UNLOCK(&prng->lock);
return err;
}
_LTC_PRNG_EXPORT(fortuna)
/**
Import a PRNG state
@ -404,10 +388,10 @@ LBL_UNLOCK:
*/
int fortuna_import(const unsigned char *in, unsigned long inlen, prng_state *prng)
{
int err, x;
int err;
LTC_ARGCHK(in != NULL);
LTC_ARGCHK(prng != NULL);
LTC_ARGCHK(in != NULL);
LTC_ARGCHK(prng != NULL);
if (inlen < (unsigned long)fortuna_desc.export_size) {
return CRYPT_INVALID_ARG;
@ -416,12 +400,12 @@ int fortuna_import(const unsigned char *in, unsigned long inlen, prng_state *prn
if ((err = fortuna_start(prng)) != CRYPT_OK) {
return err;
}
for (x = 0; x < LTC_FORTUNA_POOLS; x++) {
if ((err = fortuna_add_entropy(in+x*32, 32, prng)) != CRYPT_OK) {
return err;
}
if ((err = _fortuna_update_seed(in, inlen, prng)) != CRYPT_OK) {
return err;
}
return CRYPT_OK;
return err;
}
/**

View File

@ -153,26 +153,7 @@ int rc4_done(prng_state *prng)
@param prng The PRNG to export
@return CRYPT_OK if successful
*/
int rc4_export(unsigned char *out, unsigned long *outlen, prng_state *prng)
{
unsigned long len = rc4_desc.export_size;
LTC_ARGCHK(prng != NULL);
LTC_ARGCHK(out != NULL);
LTC_ARGCHK(outlen != NULL);
if (*outlen < len) {
*outlen = len;
return CRYPT_BUFFER_OVERFLOW;
}
if (rc4_read(out, len, prng) != len) {
return CRYPT_ERROR_READPRNG;
}
*outlen = len;
return CRYPT_OK;
}
_LTC_PRNG_EXPORT(rc4)
/**
Import a PRNG state

View File

@ -16,7 +16,12 @@
/**
Create a PRNG from a RNG
@param bits Number of bits of entropy desired (64 ... 1024)
In case you pass bits as '-1' the PRNG will be setup
as if the export/import functionality has been used,
but the imported data comes directly from the RNG.
@param bits Number of bits of entropy desired (-1 or 64 ... 1024)
@param wprng Index of which PRNG to setup
@param prng [out] PRNG state to initialize
@param callback A pointer to a void function for when the RNG is slow, this can be NULL
@ -25,7 +30,8 @@
int rng_make_prng(int bits, int wprng, prng_state *prng,
void (*callback)(void))
{
unsigned char buf[256];
unsigned char* buf;
unsigned long bytes;
int err;
LTC_ARGCHK(prng != NULL);
@ -35,31 +41,47 @@ int rng_make_prng(int bits, int wprng, prng_state *prng,
return err;
}
if (bits < 64 || bits > 1024) {
if (bits == -1) {
bytes = prng_descriptor[wprng].export_size;
} else if (bits < 64 || bits > 1024) {
return CRYPT_INVALID_PRNGSIZE;
} else {
bytes = (unsigned long)((bits+7)/8) * 2;
}
if ((err = prng_descriptor[wprng].start(prng)) != CRYPT_OK) {
return err;
}
bits = ((bits+7)/8) * 2;
if (rng_get_bytes(buf, (unsigned long)bits, callback) != (unsigned long)bits) {
return CRYPT_ERROR_READPRNG;
buf = XMALLOC(bytes);
if (buf == NULL) {
return CRYPT_MEM;
}
if ((err = prng_descriptor[wprng].add_entropy(buf, (unsigned long)bits, prng)) != CRYPT_OK) {
return err;
if (rng_get_bytes(buf, bytes, callback) != bytes) {
err = CRYPT_ERROR_READPRNG;
goto LBL_ERR;
}
if (bits == -1) {
if ((err = prng_descriptor[wprng].pimport(buf, bytes, prng)) != CRYPT_OK) {
goto LBL_ERR;
}
} else {
if ((err = prng_descriptor[wprng].add_entropy(buf, bytes, prng)) != CRYPT_OK) {
goto LBL_ERR;
}
}
if ((err = prng_descriptor[wprng].ready(prng)) != CRYPT_OK) {
return err;
goto LBL_ERR;
}
LBL_ERR:
#ifdef LTC_CLEAN_STACK
zeromem(buf, sizeof(buf));
zeromem(buf, bytes);
#endif
return CRYPT_OK;
XFREE(buf);
return err;
}
#endif /* #ifdef LTC_RNG_MAKE_PRNG */

View File

@ -152,26 +152,7 @@ int sober128_done(prng_state *prng)
@param prng The PRNG to export
@return CRYPT_OK if successful
*/
int sober128_export(unsigned char *out, unsigned long *outlen, prng_state *prng)
{
unsigned long len = sober128_desc.export_size;
LTC_ARGCHK(prng != NULL);
LTC_ARGCHK(out != NULL);
LTC_ARGCHK(outlen != NULL);
if (*outlen < len) {
*outlen = len;
return CRYPT_BUFFER_OVERFLOW;
}
if (sober128_read(out, len, prng) != len) {
return CRYPT_ERROR_READPRNG;
}
*outlen = len;
return CRYPT_OK;
}
_LTC_PRNG_EXPORT(sober128)
/**
Import a PRNG state
@ -189,7 +170,7 @@ int sober128_import(const unsigned char *in, unsigned long inlen, prng_state *pr
if (inlen < (unsigned long)sober128_desc.export_size) return CRYPT_INVALID_ARG;
if ((err = sober128_start(prng)) != CRYPT_OK) return err;
if ((err = sober128_add_entropy(in, sober128_desc.export_size, prng)) != CRYPT_OK) return err;
if ((err = sober128_add_entropy(in, inlen, prng)) != CRYPT_OK) return err;
return CRYPT_OK;
}

View File

@ -273,26 +273,7 @@ int yarrow_done(prng_state *prng)
@param prng The PRNG to export
@return CRYPT_OK if successful
*/
int yarrow_export(unsigned char *out, unsigned long *outlen, prng_state *prng)
{
unsigned long len = yarrow_desc.export_size;
LTC_ARGCHK(out != NULL);
LTC_ARGCHK(outlen != NULL);
LTC_ARGCHK(prng != NULL);
if (*outlen < len) {
*outlen = len;
return CRYPT_BUFFER_OVERFLOW;
}
if (yarrow_read(out, len, prng) != len) {
return CRYPT_ERROR_READPRNG;
}
*outlen = len;
return CRYPT_OK;
}
_LTC_PRNG_EXPORT(yarrow)
/**
Import a PRNG state

View File

@ -42,10 +42,11 @@ int prng_test(void)
before = my_test_rng_read;
if ((err = rng_make_prng(128, find_prng("yarrow"), &yarrow_prng, NULL)) != CRYPT_OK) {
if ((err = rng_make_prng(128, find_prng("yarrow"), &nprng, NULL)) != CRYPT_OK) {
fprintf(stderr, "rng_make_prng with 'my_test_rng' failed: %s\n", error_to_string(err));
exit(EXIT_FAILURE);
}
DO(yarrow_done(&nprng));
if (before == my_test_rng_read) {
fprintf(stderr, "somehow there was no read from the ltc_rng! %lu == %lu\n", before, my_test_rng_read);
@ -58,7 +59,6 @@ int prng_test(void)
/* test prngs (test, import/export) */
for (x = 0; prng_descriptor[x].name != NULL; x++) {
if(strstr(prng_descriptor[x].name, "no_prng") == prng_descriptor[x].name) continue;
err = CRYPT_OK;
DOX(prng_descriptor[x].test(), prng_descriptor[x].name);
DOX(prng_descriptor[x].start(&nprng), prng_descriptor[x].name);
DOX(prng_descriptor[x].add_entropy((unsigned char *)"helloworld12", 12, &nprng), prng_descriptor[x].name);
@ -82,6 +82,11 @@ int prng_test(void)
}
prng_descriptor[x].done(&nprng);
}
if ((err = rng_make_prng(-1, find_prng("yarrow"), &nprng, NULL)) != CRYPT_OK) {
fprintf(stderr, "rng_make_prng(-1,..) with 'yarrow' failed: %s\n", error_to_string(err));
}
DO(yarrow_done(&nprng));
return err;
}