From 04ce8cf613f635a8445b5de09cdd58847f0fcd64 Mon Sep 17 00:00:00 2001 From: Steffen Jaeckel Date: Thu, 7 Dec 2017 10:43:07 +0100 Subject: [PATCH 1/7] ensure that fortuna has been seeded properly --- src/prngs/fortuna.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/prngs/fortuna.c b/src/prngs/fortuna.c index 7b1ecb65..225eedcc 100644 --- a/src/prngs/fortuna.c +++ b/src/prngs/fortuna.c @@ -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 @@ -251,6 +254,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; From 0c05e5386f836a8cbf4bfbc12bc9113f974b6d61 Mon Sep 17 00:00:00 2001 From: Steffen Jaeckel Date: Thu, 7 Dec 2017 11:09:43 +0100 Subject: [PATCH 2/7] fortuna_import() shouldn't ignore additional input --- src/prngs/fortuna.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/prngs/fortuna.c b/src/prngs/fortuna.c index 225eedcc..e1056a13 100644 --- a/src/prngs/fortuna.c +++ b/src/prngs/fortuna.c @@ -413,6 +413,7 @@ LBL_UNLOCK: int fortuna_import(const unsigned char *in, unsigned long inlen, prng_state *prng) { int err, x; + unsigned long len; LTC_ARGCHK(in != NULL); LTC_ARGCHK(prng != NULL); @@ -424,10 +425,14 @@ 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) { + x = 0; + while (inlen > 0) { + len = MIN(inlen, 32); + if ((err = fortuna_add_entropy(in+x*32, len, prng)) != CRYPT_OK) { return err; } + x++; + inlen -= len; } return CRYPT_OK; } From d502869728298e9cc7d5261cad084e3d6cc4deea Mon Sep 17 00:00:00 2001 From: Steffen Jaeckel Date: Thu, 7 Dec 2017 11:45:19 +0100 Subject: [PATCH 3/7] don't ignore additional data on SOBER128-PRNG import --- src/prngs/sober128.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prngs/sober128.c b/src/prngs/sober128.c index 8d95491b..275920c0 100644 --- a/src/prngs/sober128.c +++ b/src/prngs/sober128.c @@ -189,7 +189,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; } From cccd1e305302cb28391765d2e5c2ddd21d41385c Mon Sep 17 00:00:00 2001 From: Steffen Jaeckel Date: Thu, 7 Dec 2017 12:00:22 +0100 Subject: [PATCH 4/7] add comment to Fortuna docs --- doc/crypt.tex | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/crypt.tex b/doc/crypt.tex index 04ccaf27..6234ae61 100644 --- a/doc/crypt.tex +++ b/doc/crypt.tex @@ -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 From 979a36b9bb9bb2cb84103cba22fea079f5534150 Mon Sep 17 00:00:00 2001 From: Steffen Jaeckel Date: Thu, 7 Dec 2017 13:26:30 +0100 Subject: [PATCH 5/7] add possibility to seed PRNG as if it's imported --- src/prngs/rng_make_prng.c | 44 +++++++++++++++++++++++++++++---------- tests/prng_test.c | 9 ++++++-- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/prngs/rng_make_prng.c b/src/prngs/rng_make_prng.c index 2bde291f..19ac1ee3 100644 --- a/src/prngs/rng_make_prng.c +++ b/src/prngs/rng_make_prng.c @@ -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 */ diff --git a/tests/prng_test.c b/tests/prng_test.c index e88ff922..a763fa06 100644 --- a/tests/prng_test.c +++ b/tests/prng_test.c @@ -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; } From 4fb3a6468ea60b697042338dbbcc733905d37f80 Mon Sep 17 00:00:00 2001 From: Steffen Jaeckel Date: Thu, 22 Mar 2018 17:50:42 +0100 Subject: [PATCH 6/7] unify the prng's export() functions This also makes fortuna_export() compliant to the "Write seed file" behavior of the original paper. --- src/headers/tomcrypt_prng.h | 25 ++++++++++++++ src/prngs/chacha20.c | 21 +----------- src/prngs/fortuna.c | 68 ++----------------------------------- src/prngs/rc4.c | 21 +----------- src/prngs/sober128.c | 21 +----------- src/prngs/yarrow.c | 21 +----------- 6 files changed, 31 insertions(+), 146 deletions(-) diff --git a/src/headers/tomcrypt_prng.h b/src/headers/tomcrypt_prng.h index c516b8cd..2f45522d 100644 --- a/src/headers/tomcrypt_prng.h +++ b/src/headers/tomcrypt_prng.h @@ -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 */ diff --git a/src/prngs/chacha20.c b/src/prngs/chacha20.c index 72a6d63d..59b23227 100644 --- a/src/prngs/chacha20.c +++ b/src/prngs/chacha20.c @@ -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 diff --git a/src/prngs/fortuna.c b/src/prngs/fortuna.c index e1056a13..9eb68b15 100644 --- a/src/prngs/fortuna.c +++ b/src/prngs/fortuna.c @@ -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, @@ -337,71 +337,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 diff --git a/src/prngs/rc4.c b/src/prngs/rc4.c index e2aa9217..7611151d 100644 --- a/src/prngs/rc4.c +++ b/src/prngs/rc4.c @@ -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 diff --git a/src/prngs/sober128.c b/src/prngs/sober128.c index 275920c0..95136595 100644 --- a/src/prngs/sober128.c +++ b/src/prngs/sober128.c @@ -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 diff --git a/src/prngs/yarrow.c b/src/prngs/yarrow.c index e5988348..6b5057f4 100644 --- a/src/prngs/yarrow.c +++ b/src/prngs/yarrow.c @@ -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 From 39d4a14c29c97f002b85038bdcdc2a788f83fe73 Mon Sep 17 00:00:00 2001 From: Steffen Jaeckel Date: Fri, 23 Mar 2018 11:30:58 +0100 Subject: [PATCH 7/7] improve fortuna_import() This makes fortuna_import() kinda compliant to the "Update seed file" behavior of the original paper. It differs from the original behavior in that it allows to import seed files which are larger than 64 bytes. --- src/prngs/fortuna.c | 61 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/src/prngs/fortuna.c b/src/prngs/fortuna.c index 9eb68b15..f65c32a1 100644 --- a/src/prngs/fortuna.c +++ b/src/prngs/fortuna.c @@ -124,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 @@ -348,11 +388,10 @@ _LTC_PRNG_EXPORT(fortuna) */ int fortuna_import(const unsigned char *in, unsigned long inlen, prng_state *prng) { - int err, x; - unsigned long len; + 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; @@ -361,16 +400,12 @@ int fortuna_import(const unsigned char *in, unsigned long inlen, prng_state *prn if ((err = fortuna_start(prng)) != CRYPT_OK) { return err; } - x = 0; - while (inlen > 0) { - len = MIN(inlen, 32); - if ((err = fortuna_add_entropy(in+x*32, len, prng)) != CRYPT_OK) { - return err; - } - x++; - inlen -= len; + + if ((err = _fortuna_update_seed(in, inlen, prng)) != CRYPT_OK) { + return err; } - return CRYPT_OK; + + return err; } /**