From d07008a0562d1f83dcab144fdec9fd920deb2b96 Mon Sep 17 00:00:00 2001 From: Guilherme Andrade Date: Sun, 12 Mar 2017 17:20:00 +0000 Subject: Support generation of strong random numbers --- lib/crypto/c_src/crypto.c | 34 +++++++++++++++++++++++++++ lib/crypto/doc/src/crypto.xml | 46 ++++++++++++++++++++++++++++++++---- lib/crypto/src/crypto.erl | 39 ++++++++++++++++++++++++++++++- lib/crypto/test/crypto_SUITE.erl | 50 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 163 insertions(+), 6 deletions(-) diff --git a/lib/crypto/c_src/crypto.c b/lib/crypto/c_src/crypto.c index b2f31870b9..0e17279e62 100644 --- a/lib/crypto/c_src/crypto.c +++ b/lib/crypto/c_src/crypto.c @@ -429,6 +429,7 @@ static ERL_NIF_TERM aes_ige_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TE static ERL_NIF_TERM aes_ctr_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM aes_ctr_stream_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM strong_rand_bytes_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM strong_rand_uniform_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM rand_uniform_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM mod_exp_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM dss_verify_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); @@ -501,6 +502,7 @@ static ErlNifFunc nif_funcs[] = { {"aes_ctr_stream_encrypt", 2, aes_ctr_stream_encrypt}, {"aes_ctr_stream_decrypt", 2, aes_ctr_stream_encrypt}, {"strong_rand_bytes_nif", 1, strong_rand_bytes_nif}, + {"strong_rand_uniform_nif", 2, strong_rand_uniform_nif}, {"rand_uniform_nif", 2, rand_uniform_nif}, {"mod_exp_nif", 4, mod_exp_nif}, {"dss_verify_nif", 4, dss_verify_nif}, @@ -2331,6 +2333,38 @@ static ERL_NIF_TERM bin_from_bn(ErlNifEnv* env, const BIGNUM *bn) return term; } +static ERL_NIF_TERM strong_rand_uniform_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Lo,Hi) */ + BIGNUM *bn_from = NULL, *bn_to, *bn_rand; + unsigned char* data; + unsigned dlen; + ERL_NIF_TERM ret; + + if (!get_bn_from_mpint(env, argv[0], &bn_from) + || !get_bn_from_mpint(env, argv[1], &bn_rand)) { + if (bn_from) BN_free(bn_from); + return enif_make_badarg(env); + } + + bn_to = BN_new(); + BN_sub(bn_to, bn_rand, bn_from); + if (BN_rand_range(bn_rand, bn_to) != 1) { + ret = atom_false; + } + else { + BN_add(bn_rand, bn_rand, bn_from); + dlen = BN_num_bytes(bn_rand); + data = enif_make_new_binary(env, dlen+4, &ret); + put_int32(data, dlen); + BN_bn2bin(bn_rand, data+4); + ERL_VALGRIND_MAKE_MEM_DEFINED(data+4, dlen); + } + BN_free(bn_rand); + BN_free(bn_from); + BN_free(bn_to); + return ret; +} + static ERL_NIF_TERM rand_uniform_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {/* (Lo,Hi) */ BIGNUM *bn_from = NULL, *bn_to, *bn_rand; diff --git a/lib/crypto/doc/src/crypto.xml b/lib/crypto/doc/src/crypto.xml index d0deaceaaf..0697f6a202 100644 --- a/lib/crypto/doc/src/crypto.xml +++ b/lib/crypto/doc/src/crypto.xml @@ -658,10 +658,13 @@

Set the seed for PRNG to the given binary. This calls the - RAND_seed function from openssl. Only use this if the system - you are running on does not have enough "randomness" built in. - Normally this is when - strong_rand_bytes/1 returns low_entropy

+ RAND_seed function from openssl. Only use this if the system + you are running on does not have enough "randomness" built in. + Normally this is when either + strong_rand_bytes/1, + strong_rand_uniform/0 or + strong_rand_uniform/1 + throws low_entropy

@@ -728,6 +731,41 @@ failed due to lack of secure "randomness".

+ + + strong_rand_uniform() -> X + Generate a random floating point number between 0.0 and 1.0 + + X = float() + + +

Generates a random floating pointer number uniformly distributed + in the value range + Uses a cryptographically secure prng seeded and periodically mixed with operating system + provided entropy. By default this is the BN_rand_range method from OpenSSL.

+

May throw exception low_entropy in case the random generator + failed due to lack of secure "randomness".

+

The generated values shall present no more than 51 bits of effective entropy.

+
+
+ + + strong_rand_uniform(N) -> X + Generate a random positive integer between 1 and N + + N = pos_integer() + X = 1..N + + +

Generates a a random positive integer uniformly distributed + in the value range + Uses a cryptographically secure prng seeded and periodically mixed with operating system + provided entropy. By default this is the BN_rand_range method from OpenSSL.

+

May throw exception low_entropy in case the random generator + failed due to lack of secure "randomness".

+
+
+ stream_init(Type, Key) -> State diff --git a/lib/crypto/src/crypto.erl b/lib/crypto/src/crypto.erl index 631af62615..4b386924cb 100644 --- a/lib/crypto/src/crypto.erl +++ b/lib/crypto/src/crypto.erl @@ -30,6 +30,8 @@ -export([hmac/3, hmac/4, hmac_init/2, hmac_update/2, hmac_final/1, hmac_final_n/2]). -export([cmac/3, cmac/4]). -export([exor/2, strong_rand_bytes/1, mod_pow/3]). +-export([strong_rand_uniform/0]). +-export([strong_rand_uniform/1]). -export([rand_uniform/2]). -export([block_encrypt/3, block_decrypt/3, block_encrypt/4, block_decrypt/4]). -export([next_iv/2, next_iv/3]). @@ -283,9 +285,11 @@ stream_decrypt(State, Data0) -> stream_crypt(fun do_stream_decrypt/2, State, Data, erlang:byte_size(Data), MaxByts, []). %% -%% RAND - pseudo random numbers using RN_ functions in crypto lib +%% RAND - pseudo random numbers using RN_ and BN_ functions in crypto lib %% -spec strong_rand_bytes(non_neg_integer()) -> binary(). +-spec strong_rand_uniform() -> float(). +-spec strong_rand_uniform(pos_integer()) -> pos_integer(). -spec rand_uniform(crypto_integer(), crypto_integer()) -> crypto_integer(). @@ -297,6 +301,38 @@ strong_rand_bytes(Bytes) -> strong_rand_bytes_nif(_Bytes) -> ?nif_stub. +strong_rand_uniform() -> + Sign = 0, % positive + Exponent = 1023, % on the interval [1.0, 2.0[ + Fraction = strong_rand_uniform(1, 1 bsl 52), % the whole interval above (except 1.0) + <> = <>, + Value - 1.0. + +strong_rand_uniform(N) when is_integer(N), N >= 1 -> + 1 + strong_rand_uniform(0, N). + +strong_rand_uniform(From, To) when is_binary(From), is_binary(To) -> + case strong_rand_uniform_nif(From,To) of + false -> + erlang:error(low_entropy); + <> when MSB > 127 -> + <<(Len + 1):32/integer, 0, MSB, Rest/binary>>; + Whatever -> + Whatever + end; +strong_rand_uniform(From, To) when is_integer(From), is_integer(To), From < To -> + BinFrom = mpint(From), + BinTo = mpint(To), + case strong_rand_uniform(BinFrom, BinTo) of + Result when is_binary(Result) -> + erlint(Result); + Other -> + Other + end. + +strong_rand_uniform_nif(_From, _To) -> ?nif_stub. + + rand_uniform(From,To) when is_binary(From), is_binary(To) -> case rand_uniform_nif(From,To) of <> when MSB > 127 -> @@ -325,6 +361,7 @@ rand_uniform_pos(_,_) -> rand_uniform_nif(_From,_To) -> ?nif_stub. + -spec rand_seed(binary()) -> ok. rand_seed(Seed) -> rand_seed_nif(Seed). diff --git a/lib/crypto/test/crypto_SUITE.erl b/lib/crypto/test/crypto_SUITE.erl index 1d7037d003..6e3a3879c4 100644 --- a/lib/crypto/test/crypto_SUITE.erl +++ b/lib/crypto/test/crypto_SUITE.erl @@ -36,7 +36,9 @@ all() -> {group, non_fips}, mod_pow, exor, - rand_uniform + rand_uniform, + strong_rand_uniform_float, + strong_rand_uniform_integer ]. groups() -> @@ -485,6 +487,44 @@ rand_uniform(Config) when is_list(Config) -> rand_uniform_aux_test(10), 10 = byte_size(crypto:strong_rand_bytes(10)). +%%-------------------------------------------------------------------- +strong_rand_uniform_float() -> + [{doc, "strong_rand_uniform float testing"}]. +strong_rand_uniform_float(Config) when is_list(Config) -> + Samples = [crypto:strong_rand_uniform() || _ <- lists:seq(1, 10000)], + allmap( + fun (V) -> + (V >= 0.0 andalso V < 1.0) + orelse {false, ct:fail({"Not in interval", V, 0.0, 1.0})} + end, + Samples). + +strong_rand_uniform_integer() -> + [{doc, "strong_rand_uniform integer testing"}]. +strong_rand_uniform_integer(Config) when is_list(Config) -> + MaxCeiling = 1 bsl 32, + Ceilings = [1 | % edge case where the ceiling equals the floor + [crypto:strong_rand_uniform(MaxCeiling) + || _ <- lists:seq(1, 99)]], + + allmap( + fun (Ceiling) -> + case Ceiling >= 1 andalso Ceiling =< MaxCeiling of + false -> + {false, ct:fail({"Ceiling not in interval", Ceiling, 1, MaxCeiling})}; + true -> + Samples = [crypto:strong_rand_uniform(Ceiling) + || _ <- lists:seq(1, 100)], + allmap( + fun (V) -> + (V >= 1 andalso V =< Ceiling) + orelse {false, ct:fail({"Sample not in interval", V, 1, Ceiling})} + end, + Samples) + end + end, + Ceilings). + %%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ %%-------------------------------------------------------------------- @@ -951,6 +991,14 @@ crypto_rand_uniform(L,H) -> ct:fail({"Not in interval", R1, L, H}) end. +allmap(_Fun, []) -> + true; +allmap(Fun, [H|T]) -> + case Fun(H) of + true -> allmap(Fun, T); + {false, Result} -> Result + end. + %%-------------------------------------------------------------------- %% Test data ------------------------------------------------ %%-------------------------------------------------------------------- -- cgit v1.2.3 From e50f63fbb2c974b4b8ad50095ca0b16a846fc161 Mon Sep 17 00:00:00 2001 From: Guilherme Andrade Date: Sat, 18 Mar 2017 12:57:19 +0000 Subject: Restyle crypto strong numeric generators for usage in rand --- lib/crypto/c_src/crypto.c | 33 +++++++++---------------- lib/crypto/doc/src/crypto.xml | 28 +++++++++++----------- lib/crypto/src/crypto.erl | 52 +++++++++++++++++----------------------- lib/crypto/test/crypto_SUITE.erl | 46 +++++++++++++++++------------------ 4 files changed, 70 insertions(+), 89 deletions(-) diff --git a/lib/crypto/c_src/crypto.c b/lib/crypto/c_src/crypto.c index 0e17279e62..b8ef08410c 100644 --- a/lib/crypto/c_src/crypto.c +++ b/lib/crypto/c_src/crypto.c @@ -429,7 +429,7 @@ static ERL_NIF_TERM aes_ige_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TE static ERL_NIF_TERM aes_ctr_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM aes_ctr_stream_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM strong_rand_bytes_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM strong_rand_uniform_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM strong_rand_range_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM rand_uniform_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM mod_exp_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM dss_verify_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); @@ -502,7 +502,7 @@ static ErlNifFunc nif_funcs[] = { {"aes_ctr_stream_encrypt", 2, aes_ctr_stream_encrypt}, {"aes_ctr_stream_decrypt", 2, aes_ctr_stream_encrypt}, {"strong_rand_bytes_nif", 1, strong_rand_bytes_nif}, - {"strong_rand_uniform_nif", 2, strong_rand_uniform_nif}, + {"strong_rand_range_nif", 1, strong_rand_range_nif}, {"rand_uniform_nif", 2, rand_uniform_nif}, {"mod_exp_nif", 4, mod_exp_nif}, {"dss_verify_nif", 4, dss_verify_nif}, @@ -2333,35 +2333,24 @@ static ERL_NIF_TERM bin_from_bn(ErlNifEnv* env, const BIGNUM *bn) return term; } -static ERL_NIF_TERM strong_rand_uniform_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Lo,Hi) */ - BIGNUM *bn_from = NULL, *bn_to, *bn_rand; - unsigned char* data; - unsigned dlen; +static ERL_NIF_TERM strong_rand_range_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Range) */ + BIGNUM *bn_range, *bn_rand; ERL_NIF_TERM ret; - if (!get_bn_from_mpint(env, argv[0], &bn_from) - || !get_bn_from_mpint(env, argv[1], &bn_rand)) { - if (bn_from) BN_free(bn_from); - return enif_make_badarg(env); + if(!get_bn_from_bin(env, argv[0], &bn_range)) { + return enif_make_badarg(env); } - bn_to = BN_new(); - BN_sub(bn_to, bn_rand, bn_from); - if (BN_rand_range(bn_rand, bn_to) != 1) { + bn_rand = BN_new(); + if (BN_rand_range(bn_rand, bn_range) != 1) { ret = atom_false; } else { - BN_add(bn_rand, bn_rand, bn_from); - dlen = BN_num_bytes(bn_rand); - data = enif_make_new_binary(env, dlen+4, &ret); - put_int32(data, dlen); - BN_bn2bin(bn_rand, data+4); - ERL_VALGRIND_MAKE_MEM_DEFINED(data+4, dlen); + ret = bin_from_bn(env, bn_rand); } BN_free(bn_rand); - BN_free(bn_from); - BN_free(bn_to); + BN_free(bn_range); return ret; } diff --git a/lib/crypto/doc/src/crypto.xml b/lib/crypto/doc/src/crypto.xml index 0697f6a202..dc725a41be 100644 --- a/lib/crypto/doc/src/crypto.xml +++ b/lib/crypto/doc/src/crypto.xml @@ -662,8 +662,8 @@ you are running on does not have enough "randomness" built in. Normally this is when either strong_rand_bytes/1, - strong_rand_uniform/0 or - strong_rand_uniform/1 + strong_rand_range/1 or + strong_rand_float/0 throws low_entropy

@@ -733,36 +733,36 @@ - strong_rand_uniform() -> X - Generate a random floating point number between 0.0 and 1.0 + strong_rand_range(N) -> binary() + Generate a random non-negative integer between 0 and N - X = float() + N = pos_integer() | binary() -

Generates a random floating pointer number uniformly distributed - in the value range +

Generates a random non-negative integer uniformly distributed + in the value range Uses a cryptographically secure prng seeded and periodically mixed with operating system provided entropy. By default this is the BN_rand_range method from OpenSSL.

+

Returns binary representation.

May throw exception low_entropy in case the random generator failed due to lack of secure "randomness".

-

The generated values shall present no more than 51 bits of effective entropy.

- strong_rand_uniform(N) -> X - Generate a random positive integer between 1 and N + strong_rand_float() -> X + Generate a random floating point number between 0.0 and 1.0 - N = pos_integer() - X = 1..N + X = float() -

Generates a a random positive integer uniformly distributed - in the value range +

Generates a random floating pointer number uniformly distributed + in the value range Uses a cryptographically secure prng seeded and periodically mixed with operating system provided entropy. By default this is the BN_rand_range method from OpenSSL.

May throw exception low_entropy in case the random generator failed due to lack of secure "randomness".

+

The generated values shall present no more than 51 bits of effective entropy.

diff --git a/lib/crypto/src/crypto.erl b/lib/crypto/src/crypto.erl index 4b386924cb..2c3208a3d5 100644 --- a/lib/crypto/src/crypto.erl +++ b/lib/crypto/src/crypto.erl @@ -30,8 +30,8 @@ -export([hmac/3, hmac/4, hmac_init/2, hmac_update/2, hmac_final/1, hmac_final_n/2]). -export([cmac/3, cmac/4]). -export([exor/2, strong_rand_bytes/1, mod_pow/3]). --export([strong_rand_uniform/0]). --export([strong_rand_uniform/1]). +-export([strong_rand_range/1]). +-export([strong_rand_float/0]). -export([rand_uniform/2]). -export([block_encrypt/3, block_decrypt/3, block_encrypt/4, block_decrypt/4]). -export([next_iv/2, next_iv/3]). @@ -288,8 +288,8 @@ stream_decrypt(State, Data0) -> %% RAND - pseudo random numbers using RN_ and BN_ functions in crypto lib %% -spec strong_rand_bytes(non_neg_integer()) -> binary(). --spec strong_rand_uniform() -> float(). --spec strong_rand_uniform(pos_integer()) -> pos_integer(). +-spec strong_rand_range(pos_integer() | binary()) -> binary(). +-spec strong_rand_float() -> float(). -spec rand_uniform(crypto_integer(), crypto_integer()) -> crypto_integer(). @@ -301,36 +301,28 @@ strong_rand_bytes(Bytes) -> strong_rand_bytes_nif(_Bytes) -> ?nif_stub. -strong_rand_uniform() -> - Sign = 0, % positive - Exponent = 1023, % on the interval [1.0, 2.0[ - Fraction = strong_rand_uniform(1, 1 bsl 52), % the whole interval above (except 1.0) - <> = <>, - Value - 1.0. - -strong_rand_uniform(N) when is_integer(N), N >= 1 -> - 1 + strong_rand_uniform(0, N). - -strong_rand_uniform(From, To) when is_binary(From), is_binary(To) -> - case strong_rand_uniform_nif(From,To) of +strong_rand_range(Range) when is_integer(Range), Range > 0 -> + BinRange = int_to_bin(Range), + strong_rand_range(BinRange); +strong_rand_range(BinRange) when is_binary(BinRange) -> + case strong_rand_range_nif(BinRange) of false -> erlang:error(low_entropy); - <> when MSB > 127 -> - <<(Len + 1):32/integer, 0, MSB, Rest/binary>>; - Whatever -> - Whatever - end; -strong_rand_uniform(From, To) when is_integer(From), is_integer(To), From < To -> - BinFrom = mpint(From), - BinTo = mpint(To), - case strong_rand_uniform(BinFrom, BinTo) of - Result when is_binary(Result) -> - erlint(Result); - Other -> - Other + <> -> + BinResult end. -strong_rand_uniform_nif(_From, _To) -> ?nif_stub. +strong_rand_range_nif(_BinRange) -> ?nif_stub. + + +strong_rand_float() -> + % This could be optimized by having its own NIF + Sign = 0, % positive + Exponent = 1023, % on the interval [1.0, 2.0[ + BinFraction = strong_rand_range(1 bsl 52), % the whole interval above + Fraction = bin_to_int(BinFraction), + <> = <>, + Value - 1.0. rand_uniform(From,To) when is_binary(From), is_binary(To) -> diff --git a/lib/crypto/test/crypto_SUITE.erl b/lib/crypto/test/crypto_SUITE.erl index 6e3a3879c4..0d80786fbc 100644 --- a/lib/crypto/test/crypto_SUITE.erl +++ b/lib/crypto/test/crypto_SUITE.erl @@ -37,8 +37,8 @@ all() -> mod_pow, exor, rand_uniform, - strong_rand_uniform_float, - strong_rand_uniform_integer + strong_rand_range, + strong_rand_float ]. groups() -> @@ -488,43 +488,43 @@ rand_uniform(Config) when is_list(Config) -> 10 = byte_size(crypto:strong_rand_bytes(10)). %%-------------------------------------------------------------------- -strong_rand_uniform_float() -> - [{doc, "strong_rand_uniform float testing"}]. -strong_rand_uniform_float(Config) when is_list(Config) -> - Samples = [crypto:strong_rand_uniform() || _ <- lists:seq(1, 10000)], - allmap( - fun (V) -> - (V >= 0.0 andalso V < 1.0) - orelse {false, ct:fail({"Not in interval", V, 0.0, 1.0})} - end, - Samples). - -strong_rand_uniform_integer() -> - [{doc, "strong_rand_uniform integer testing"}]. -strong_rand_uniform_integer(Config) when is_list(Config) -> +strong_rand_range() -> + [{doc, "strong_rand_range testing"}]. +strong_rand_range(Config) when is_list(Config) -> MaxCeiling = 1 bsl 32, - Ceilings = [1 | % edge case where the ceiling equals the floor - [crypto:strong_rand_uniform(MaxCeiling) + Ceilings = [1 | % edge case where only 0 can be generated + [binary:decode_unsigned(crypto:strong_rand_range(MaxCeiling), big) || _ <- lists:seq(1, 99)]], allmap( fun (Ceiling) -> - case Ceiling >= 1 andalso Ceiling =< MaxCeiling of + case Ceiling >= 0 andalso Ceiling < MaxCeiling of false -> - {false, ct:fail({"Ceiling not in interval", Ceiling, 1, MaxCeiling})}; + {false, ct:fail({"Ceiling not in interval", Ceiling, 0, MaxCeiling})}; true -> - Samples = [crypto:strong_rand_uniform(Ceiling) + Samples = [binary:decode_unsigned(crypto:strong_rand_range(Ceiling), big) || _ <- lists:seq(1, 100)], allmap( fun (V) -> - (V >= 1 andalso V =< Ceiling) - orelse {false, ct:fail({"Sample not in interval", V, 1, Ceiling})} + (V >= 0 andalso V < Ceiling) + orelse {false, ct:fail({"Sample not in interval", V, 0, Ceiling})} end, Samples) end end, Ceilings). +strong_rand_float() -> + [{doc, "strong_rand_float testing"}]. +strong_rand_float(Config) when is_list(Config) -> + Samples = [crypto:strong_rand_float() || _ <- lists:seq(1, 10000)], + allmap( + fun (V) -> + (V >= 0.0 andalso V < 1.0) + orelse {false, ct:fail({"Not in interval", V, 0.0, 1.0})} + end, + Samples). + %%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ %%-------------------------------------------------------------------- -- cgit v1.2.3 From 77039e648c8a62bfc4f0242531d5fd4874b29aad Mon Sep 17 00:00:00 2001 From: Guilherme Andrade Date: Sat, 18 Mar 2017 17:27:35 +0000 Subject: Support cryptographically strong rand plugin --- lib/crypto/doc/src/crypto.xml | 29 +++++++++++ lib/crypto/src/crypto.erl | 30 +++++++++++- lib/crypto/test/crypto_SUITE.erl | 102 ++++++++++++++++++++++++++++++++++++++- lib/stdlib/doc/src/rand.xml | 24 ++++++--- lib/stdlib/src/rand.erl | 36 ++++++++------ 5 files changed, 195 insertions(+), 26 deletions(-) diff --git a/lib/crypto/doc/src/crypto.xml b/lib/crypto/doc/src/crypto.xml index dc725a41be..7a5bd62c26 100644 --- a/lib/crypto/doc/src/crypto.xml +++ b/lib/crypto/doc/src/crypto.xml @@ -766,6 +766,35 @@ + + rand_seed() -> rand:state() + Strong random number generation plugin state> + + Creates state object for random number generation, + in order to generate cryptographically strong random numbers + (based on OpenSSL's BN_rand_range), + and saves it on process dictionary before returning it as well. + See also rand:seed/1 + +

Example

+
+crypto:rand_seed(),
+_IntegerValue = rand:uniform(42), % [1; 42]
+_FloatValue = rand:uniform().     % [0.0; 1.0]
+
+
+ + + rand_seed_s() -> rand:state() + Strong random number generation plugin state> + + Creates state object for random number generation, + in order to generate cryptographically strongly random numbers + (based on OpenSSL's BN_rand_range). + See also rand:seed_s/1 + + + stream_init(Type, Key) -> State diff --git a/lib/crypto/src/crypto.erl b/lib/crypto/src/crypto.erl index 2c3208a3d5..4ae7a9cdd6 100644 --- a/lib/crypto/src/crypto.erl +++ b/lib/crypto/src/crypto.erl @@ -32,6 +32,8 @@ -export([exor/2, strong_rand_bytes/1, mod_pow/3]). -export([strong_rand_range/1]). -export([strong_rand_float/0]). +-export([rand_seed/0]). +-export([rand_seed_s/0]). -export([rand_uniform/2]). -export([block_encrypt/3, block_decrypt/3, block_encrypt/4, block_decrypt/4]). -export([next_iv/2, next_iv/3]). @@ -290,6 +292,8 @@ stream_decrypt(State, Data0) -> -spec strong_rand_bytes(non_neg_integer()) -> binary(). -spec strong_rand_range(pos_integer() | binary()) -> binary(). -spec strong_rand_float() -> float(). +-spec rand_seed() -> rand:state(). +-spec rand_seed_s() -> rand:state(). -spec rand_uniform(crypto_integer(), crypto_integer()) -> crypto_integer(). @@ -311,7 +315,6 @@ strong_rand_range(BinRange) when is_binary(BinRange) -> <> -> BinResult end. - strong_rand_range_nif(_BinRange) -> ?nif_stub. @@ -325,6 +328,31 @@ strong_rand_float() -> Value - 1.0. +rand_seed() -> + rand:seed(rand_seed_s()). + +rand_seed_s() -> + {#{ type => crypto, + max => infinity, + next => fun rand_plugin_next/1, + uniform => fun rand_plugin_uniform/1, + uniform_n => fun rand_plugin_uniform/2, + jump => fun rand_plugin_jump/1}, + no_seed}. + +rand_plugin_next(Seed) -> + {bytes_to_integer(strong_rand_range(1 bsl 64)), Seed}. + +rand_plugin_uniform(State) -> + {strong_rand_float(), State}. + +rand_plugin_uniform(Max, State) -> + {bytes_to_integer(strong_rand_range(Max)) + 1, State}. + +rand_plugin_jump(State) -> + State. + + rand_uniform(From,To) when is_binary(From), is_binary(To) -> case rand_uniform_nif(From,To) of <> when MSB > 127 -> diff --git a/lib/crypto/test/crypto_SUITE.erl b/lib/crypto/test/crypto_SUITE.erl index 0d80786fbc..482a07d634 100644 --- a/lib/crypto/test/crypto_SUITE.erl +++ b/lib/crypto/test/crypto_SUITE.erl @@ -38,7 +38,9 @@ all() -> exor, rand_uniform, strong_rand_range, - strong_rand_float + strong_rand_float, + rand_plugin, + rand_plugin_s ]. groups() -> @@ -525,6 +527,17 @@ strong_rand_float(Config) when is_list(Config) -> end, Samples). +%%-------------------------------------------------------------------- +rand_plugin() -> + [{doc, "crypto rand plugin testing (implicit state / process dictionary)"}]. +rand_plugin(Config) when is_list(Config) -> + rand_plugin_aux(implicit_state). + +rand_plugin_s() -> + [{doc, "crypto rand plugin testing (explicit state)"}]. +rand_plugin_s(Config) when is_list(Config) -> + rand_plugin_aux(explicit_state). + %%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ %%-------------------------------------------------------------------- @@ -991,6 +1004,14 @@ crypto_rand_uniform(L,H) -> ct:fail({"Not in interval", R1, L, H}) end. +foldallmap(_Fun, AccN, []) -> + {true, AccN}; +foldallmap(Fun, AccN, [H|T]) -> + case Fun(H, AccN) of + {true, AccM} -> foldallmap(Fun, AccM, T); + {{false, Result}, AccM} -> {Result, AccM} + end. + allmap(_Fun, []) -> true; allmap(Fun, [H|T]) -> @@ -999,6 +1020,85 @@ allmap(Fun, [H|T]) -> {false, Result} -> Result end. +rand_plugin_aux(StateType) -> + {Seeder, SeedExporter, FloatGenerator, IntegerGenerator} = rand_plugin_functions(StateType), + State0 = Seeder(), + {crypto, no_seed} = SeedExporter(State0), + {FloatTestResult, State1} = rand_plugin_aux_floats(State0, FloatGenerator), + case FloatTestResult of + true -> + {IntegerTestResult, _State2} = rand_plugin_aux_integers(State1, IntegerGenerator), + IntegerTestResult; + {false, _} -> + FloatTestResult + end. + +% returns {Seeder, SeedExporter, FloatGenerator, IntegerGenerator} with consistent signatures +rand_plugin_functions(implicit_state) -> + {fun () -> crypto:rand_seed(), implicit_state end, + fun (implicit_state) -> rand:export_seed() end, + fun (implicit_state) -> {rand:uniform(), implicit_state} end, + fun (N, implicit_state) -> {rand:uniform(N), implicit_state} end}; +rand_plugin_functions(explicit_state) -> + {fun crypto:rand_seed_s/0, + fun rand:export_seed_s/1, + fun rand:uniform_s/1, + fun rand:uniform_s/2}. + +rand_plugin_aux_floats(State0, FloatGenerator) -> + {FloatSamples, State1} = + lists:mapfoldl( + fun (_, StateAcc) -> + FloatGenerator(StateAcc) + end, + State0, + lists:seq(1, 10000)), + + {allmap( + fun (V) -> + (V >= 0.0 andalso V < 1.0) + orelse {false, ct:fail({"Float sample not in interval", V, 0.0, 1.0})} + end, + FloatSamples), + State1}. + +rand_plugin_aux_integers(State0, IntegerGenerator) -> + MaxIntegerCeiling = 1 bsl 32, + {IntegerCeilings, State1} = + lists:mapfoldl( + fun (_, StateAcc) -> + IntegerGenerator(MaxIntegerCeiling, StateAcc) + end, + State0, + lists:seq(1, 100)), + + foldallmap( + fun (Ceiling, StateAcc) -> + case Ceiling >= 1 andalso Ceiling =< MaxIntegerCeiling of + false -> + {{false, ct:fail({"Integer ceiling not in interval", + Ceiling, 1, MaxIntegerCeiling})}, + StateAcc}; + true -> + foldallmap( + fun (_, SubStateAcc) -> + {Sample, NewSubStateAcc} = IntegerGenerator(Ceiling, SubStateAcc), + case Sample >= 1 andalso Sample =< Ceiling of + false -> + {{false, ct:fail({"Integer sample not in interval", + Sample, 1, Ceiling})}, + NewSubStateAcc}; + true -> + {true, NewSubStateAcc} + end + end, + StateAcc, + lists:seq(1, 100)) + end + end, + State1, + IntegerCeilings). + %%-------------------------------------------------------------------- %% Test data ------------------------------------------------ %%-------------------------------------------------------------------- diff --git a/lib/stdlib/doc/src/rand.xml b/lib/stdlib/doc/src/rand.xml index 8745e16908..e7a5fb7fab 100644 --- a/lib/stdlib/doc/src/rand.xml +++ b/lib/stdlib/doc/src/rand.xml @@ -120,19 +120,27 @@ S0 = rand:seed_s(exsplus), {SND0, S2} = rand:normal_s(S1), -

This random number generator is not cryptographically - strong. If a strong cryptographic random number generator is - needed, use one of functions in the - crypto - module, for example, - crypto:strong_rand_bytes/1.

+

The builtin random number generator algorithms are not + cryptographically strong. If a cryptographically strong + random number generator is needed, use something like + crypto:rand_seed/0. +

+ + + + + + + + +

Algorithm-dependent state.

@@ -216,7 +224,7 @@ S0 = rand:seed_s(exsplus),

Seeds random number generation with the specifed algorithm and - time-dependent data if AlgOrExpState is an algorithm.

+ time-dependent data if AlgOrStateOrExpState is an algorithm.

Otherwise recreates the exported seed in the process dictionary, and returns the state. See also export_seed/0.

@@ -237,7 +245,7 @@ S0 = rand:seed_s(exsplus), Seed random number generator.

Seeds random number generation with the specifed algorithm and - time-dependent data if AlgOrExpState is an algorithm.

+ time-dependent data if AlgOrStateOrExpState is an algorithm.

Otherwise recreates the exported seed and returns the state. See also export_seed/0.

diff --git a/lib/stdlib/src/rand.erl b/lib/stdlib/src/rand.erl index 1f457b9e0e..3ff7aaf15f 100644 --- a/lib/stdlib/src/rand.erl +++ b/lib/stdlib/src/rand.erl @@ -45,20 +45,22 @@ %% ===================================================================== %% This depends on the algorithm handler function --type alg_seed() :: exs64_state() | exsplus_state() | exs1024_state(). +-type alg_seed() :: exs64_state() | exsplus_state() | exs1024_state() | term(). + %% This is the algorithm handler function within this module -type alg_handler() :: #{type := alg(), - max := integer(), - next := fun(), - uniform := fun(), - uniform_n := fun(), - jump := fun()}. - -%% Internal state --opaque state() :: {alg_handler(), alg_seed()}. --type alg() :: exs64 | exsplus | exs1024. --opaque export_state() :: {alg(), alg_seed()}. --export_type([alg/0, state/0, export_state/0]). + max := integer() | infinity, + next := fun((alg_seed()) -> {uint64(), alg_seed()}), + uniform := fun((state()) -> {float(), state()}), + uniform_n := fun((pos_integer(), state()) -> {pos_integer(), state()}), + jump := fun((state()) -> state())}. + +%% Algorithm state +-type state() :: {alg_handler(), alg_seed()}. +-type builtin_alg() :: exs64 | exsplus | exs1024. +-type alg() :: builtin_alg() | term(). +-type export_state() :: {alg(), alg_seed()}. +-export_type([builtin_alg/0, alg/0, alg_handler/0, alg_seed/0, state/0, export_state/0]). %% ===================================================================== %% API @@ -81,15 +83,17 @@ export_seed_s({#{type:=Alg}, Seed}) -> {Alg, Seed}. %% seed({Alg,Seed}) setup RNG with a previously exported seed %% and return the NEW state --spec seed(AlgOrExpState::alg() | export_state()) -> state(). +-spec seed(AlgOrStateOrExpState::builtin_alg() | state() | export_state()) -> state(). seed(Alg) -> seed_put(seed_s(Alg)). --spec seed_s(AlgOrExpState::alg() | export_state()) -> state(). +-spec seed_s(AlgOrStateOrExpState::builtin_alg() | state() | export_state()) -> state(). seed_s(Alg) when is_atom(Alg) -> seed_s(Alg, {erlang:phash2([{node(),self()}]), erlang:system_time(), erlang:unique_integer()}); +seed_s({AlgHandler, _Seed} = State) when is_map(AlgHandler) -> + State; seed_s({Alg0, Seed}) -> {Alg,_SeedFun} = mk_alg(Alg0), {Alg, Seed}. @@ -97,11 +101,11 @@ seed_s({Alg0, Seed}) -> %% seed/2: seeds RNG with the algorithm and given values %% and returns the NEW state. --spec seed(Alg :: alg(), {integer(), integer(), integer()}) -> state(). +-spec seed(Alg :: builtin_alg(), {integer(), integer(), integer()}) -> state(). seed(Alg0, S0) -> seed_put(seed_s(Alg0, S0)). --spec seed_s(Alg :: alg(), {integer(), integer(), integer()}) -> state(). +-spec seed_s(Alg :: builtin_alg(), {integer(), integer(), integer()}) -> state(). seed_s(Alg0, S0 = {_, _, _}) -> {Alg, Seed} = mk_alg(Alg0), AS = Seed(S0), -- cgit v1.2.3 From 5eae0dacf40ec60b09f0fdf761987e39320c4db0 Mon Sep 17 00:00:00 2001 From: Guilherme Andrade Date: Sat, 18 Mar 2017 17:32:23 +0000 Subject: No longer expose strong_rand_(range|float) --- lib/crypto/doc/src/crypto.xml | 40 ++------------------------------- lib/crypto/src/crypto.erl | 48 ++++++++++++++++++---------------------- lib/crypto/test/crypto_SUITE.erl | 40 --------------------------------- 3 files changed, 23 insertions(+), 105 deletions(-) diff --git a/lib/crypto/doc/src/crypto.xml b/lib/crypto/doc/src/crypto.xml index 7a5bd62c26..36a1a2c2ee 100644 --- a/lib/crypto/doc/src/crypto.xml +++ b/lib/crypto/doc/src/crypto.xml @@ -660,10 +660,8 @@

Set the seed for PRNG to the given binary. This calls the RAND_seed function from openssl. Only use this if the system you are running on does not have enough "randomness" built in. - Normally this is when either - strong_rand_bytes/1, - strong_rand_range/1 or - strong_rand_float/0 + Normally this is when + strong_rand_bytes/1 throws low_entropy

@@ -732,40 +730,6 @@ - - strong_rand_range(N) -> binary() - Generate a random non-negative integer between 0 and N - - N = pos_integer() | binary() - - -

Generates a random non-negative integer uniformly distributed - in the value range - Uses a cryptographically secure prng seeded and periodically mixed with operating system - provided entropy. By default this is the BN_rand_range method from OpenSSL.

-

Returns binary representation.

-

May throw exception low_entropy in case the random generator - failed due to lack of secure "randomness".

-
-
- - - strong_rand_float() -> X - Generate a random floating point number between 0.0 and 1.0 - - X = float() - - -

Generates a random floating pointer number uniformly distributed - in the value range - Uses a cryptographically secure prng seeded and periodically mixed with operating system - provided entropy. By default this is the BN_rand_range method from OpenSSL.

-

May throw exception low_entropy in case the random generator - failed due to lack of secure "randomness".

-

The generated values shall present no more than 51 bits of effective entropy.

-
-
- rand_seed() -> rand:state() Strong random number generation plugin state> diff --git a/lib/crypto/src/crypto.erl b/lib/crypto/src/crypto.erl index 4ae7a9cdd6..ad9245f8f2 100644 --- a/lib/crypto/src/crypto.erl +++ b/lib/crypto/src/crypto.erl @@ -30,8 +30,6 @@ -export([hmac/3, hmac/4, hmac_init/2, hmac_update/2, hmac_final/1, hmac_final_n/2]). -export([cmac/3, cmac/4]). -export([exor/2, strong_rand_bytes/1, mod_pow/3]). --export([strong_rand_range/1]). --export([strong_rand_float/0]). -export([rand_seed/0]). -export([rand_seed_s/0]). -export([rand_uniform/2]). @@ -290,8 +288,6 @@ stream_decrypt(State, Data0) -> %% RAND - pseudo random numbers using RN_ and BN_ functions in crypto lib %% -spec strong_rand_bytes(non_neg_integer()) -> binary(). --spec strong_rand_range(pos_integer() | binary()) -> binary(). --spec strong_rand_float() -> float(). -spec rand_seed() -> rand:state(). -spec rand_seed_s() -> rand:state(). -spec rand_uniform(crypto_integer(), crypto_integer()) -> @@ -305,29 +301,6 @@ strong_rand_bytes(Bytes) -> strong_rand_bytes_nif(_Bytes) -> ?nif_stub. -strong_rand_range(Range) when is_integer(Range), Range > 0 -> - BinRange = int_to_bin(Range), - strong_rand_range(BinRange); -strong_rand_range(BinRange) when is_binary(BinRange) -> - case strong_rand_range_nif(BinRange) of - false -> - erlang:error(low_entropy); - <> -> - BinResult - end. -strong_rand_range_nif(_BinRange) -> ?nif_stub. - - -strong_rand_float() -> - % This could be optimized by having its own NIF - Sign = 0, % positive - Exponent = 1023, % on the interval [1.0, 2.0[ - BinFraction = strong_rand_range(1 bsl 52), % the whole interval above - Fraction = bin_to_int(BinFraction), - <> = <>, - Value - 1.0. - - rand_seed() -> rand:seed(rand_seed_s()). @@ -352,6 +325,27 @@ rand_plugin_uniform(Max, State) -> rand_plugin_jump(State) -> State. +strong_rand_range(Range) when is_integer(Range), Range > 0 -> + BinRange = int_to_bin(Range), + strong_rand_range(BinRange); +strong_rand_range(BinRange) when is_binary(BinRange) -> + case strong_rand_range_nif(BinRange) of + false -> + erlang:error(low_entropy); + <> -> + BinResult + end. +strong_rand_range_nif(_BinRange) -> ?nif_stub. + +strong_rand_float() -> + % This could be optimized by having its own NIF + Sign = 0, % positive + Exponent = 1023, % on the interval [1.0, 2.0[ + BinFraction = strong_rand_range(1 bsl 52), % the whole interval above + Fraction = bin_to_int(BinFraction), + <> = <>, + Value - 1.0. + rand_uniform(From,To) when is_binary(From), is_binary(To) -> case rand_uniform_nif(From,To) of diff --git a/lib/crypto/test/crypto_SUITE.erl b/lib/crypto/test/crypto_SUITE.erl index 482a07d634..1b7456af18 100644 --- a/lib/crypto/test/crypto_SUITE.erl +++ b/lib/crypto/test/crypto_SUITE.erl @@ -37,8 +37,6 @@ all() -> mod_pow, exor, rand_uniform, - strong_rand_range, - strong_rand_float, rand_plugin, rand_plugin_s ]. @@ -489,44 +487,6 @@ rand_uniform(Config) when is_list(Config) -> rand_uniform_aux_test(10), 10 = byte_size(crypto:strong_rand_bytes(10)). -%%-------------------------------------------------------------------- -strong_rand_range() -> - [{doc, "strong_rand_range testing"}]. -strong_rand_range(Config) when is_list(Config) -> - MaxCeiling = 1 bsl 32, - Ceilings = [1 | % edge case where only 0 can be generated - [binary:decode_unsigned(crypto:strong_rand_range(MaxCeiling), big) - || _ <- lists:seq(1, 99)]], - - allmap( - fun (Ceiling) -> - case Ceiling >= 0 andalso Ceiling < MaxCeiling of - false -> - {false, ct:fail({"Ceiling not in interval", Ceiling, 0, MaxCeiling})}; - true -> - Samples = [binary:decode_unsigned(crypto:strong_rand_range(Ceiling), big) - || _ <- lists:seq(1, 100)], - allmap( - fun (V) -> - (V >= 0 andalso V < Ceiling) - orelse {false, ct:fail({"Sample not in interval", V, 0, Ceiling})} - end, - Samples) - end - end, - Ceilings). - -strong_rand_float() -> - [{doc, "strong_rand_float testing"}]. -strong_rand_float(Config) when is_list(Config) -> - Samples = [crypto:strong_rand_float() || _ <- lists:seq(1, 10000)], - allmap( - fun (V) -> - (V >= 0.0 andalso V < 1.0) - orelse {false, ct:fail({"Not in interval", V, 0.0, 1.0})} - end, - Samples). - %%-------------------------------------------------------------------- rand_plugin() -> [{doc, "crypto rand plugin testing (implicit state / process dictionary)"}]. -- cgit v1.2.3 From 1f236ff5a8aaedc720b45420c3d53092435ecd52 Mon Sep 17 00:00:00 2001 From: Guilherme Andrade Date: Wed, 22 Mar 2017 21:48:54 +0000 Subject: fixup! Support cryptographically strong rand plugin Be friendlier to Dialyzer --- lib/crypto/doc/src/crypto.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/crypto/doc/src/crypto.xml b/lib/crypto/doc/src/crypto.xml index 36a1a2c2ee..3eee24769d 100644 --- a/lib/crypto/doc/src/crypto.xml +++ b/lib/crypto/doc/src/crypto.xml @@ -742,7 +742,7 @@

Example

-crypto:rand_seed(),
+_ = crypto:rand_seed(),
 _IntegerValue = rand:uniform(42), % [1; 42]
 _FloatValue = rand:uniform().     % [0.0; 1.0]
-- cgit v1.2.3 From 6f6c478401eefee1c8d7b200eb02d288739a7648 Mon Sep 17 00:00:00 2001 From: Guilherme Andrade Date: Wed, 22 Mar 2017 21:49:38 +0000 Subject: fixup! Support cryptographically strong rand plugin Fix documented range (interval is half-open.) --- lib/crypto/doc/src/crypto.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/crypto/doc/src/crypto.xml b/lib/crypto/doc/src/crypto.xml index 3eee24769d..30835a8447 100644 --- a/lib/crypto/doc/src/crypto.xml +++ b/lib/crypto/doc/src/crypto.xml @@ -744,7 +744,7 @@
 _ = crypto:rand_seed(),
 _IntegerValue = rand:uniform(42), % [1; 42]
-_FloatValue = rand:uniform().     % [0.0; 1.0]
+_FloatValue = rand:uniform(). % [0.0; 1.0[
-- cgit v1.2.3 From 54b89c8750bed431275f1f376fb10ec7899ca335 Mon Sep 17 00:00:00 2001 From: Guilherme Andrade Date: Wed, 22 Mar 2017 21:50:20 +0000 Subject: fixup! Support cryptographically strong rand plugin Fix plugin alg type --- lib/stdlib/src/rand.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/stdlib/src/rand.erl b/lib/stdlib/src/rand.erl index 3ff7aaf15f..ddd22914e2 100644 --- a/lib/stdlib/src/rand.erl +++ b/lib/stdlib/src/rand.erl @@ -58,7 +58,7 @@ %% Algorithm state -type state() :: {alg_handler(), alg_seed()}. -type builtin_alg() :: exs64 | exsplus | exs1024. --type alg() :: builtin_alg() | term(). +-type alg() :: builtin_alg() | atom(). -type export_state() :: {alg(), alg_seed()}. -export_type([builtin_alg/0, alg/0, alg_handler/0, alg_seed/0, state/0, export_state/0]). -- cgit v1.2.3 From 195edd9076c865f8af6a9aeeb02bc032b94c0fb3 Mon Sep 17 00:00:00 2001 From: Guilherme Andrade Date: Wed, 22 Mar 2017 21:51:17 +0000 Subject: fixup! Support cryptographically strong rand plugin Minimize use of guards. --- lib/stdlib/src/rand.erl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/stdlib/src/rand.erl b/lib/stdlib/src/rand.erl index ddd22914e2..60da53cd2b 100644 --- a/lib/stdlib/src/rand.erl +++ b/lib/stdlib/src/rand.erl @@ -88,15 +88,15 @@ seed(Alg) -> seed_put(seed_s(Alg)). -spec seed_s(AlgOrStateOrExpState::builtin_alg() | state() | export_state()) -> state(). -seed_s(Alg) when is_atom(Alg) -> - seed_s(Alg, {erlang:phash2([{node(),self()}]), - erlang:system_time(), - erlang:unique_integer()}); seed_s({AlgHandler, _Seed} = State) when is_map(AlgHandler) -> State; seed_s({Alg0, Seed}) -> {Alg,_SeedFun} = mk_alg(Alg0), - {Alg, Seed}. + {Alg, Seed}; +seed_s(Alg) -> + seed_s(Alg, {erlang:phash2([{node(),self()}]), + erlang:system_time(), + erlang:unique_integer()}). %% seed/2: seeds RNG with the algorithm and given values %% and returns the NEW state. -- cgit v1.2.3 From ec1e5bc7ff8101b7db4fa5d67bbbed652a9238c8 Mon Sep 17 00:00:00 2001 From: Guilherme Andrade Date: Wed, 22 Mar 2017 21:56:28 +0000 Subject: Allow for crypto upgrades when using rand plugin --- lib/crypto/src/crypto.erl | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/crypto/src/crypto.erl b/lib/crypto/src/crypto.erl index ad9245f8f2..777b5cd1c6 100644 --- a/lib/crypto/src/crypto.erl +++ b/lib/crypto/src/crypto.erl @@ -32,6 +32,10 @@ -export([exor/2, strong_rand_bytes/1, mod_pow/3]). -export([rand_seed/0]). -export([rand_seed_s/0]). +-export([rand_plugin_next/1]). +-export([rand_plugin_uniform/1]). +-export([rand_plugin_uniform/2]). +-export([rand_plugin_jump/1]). -export([rand_uniform/2]). -export([block_encrypt/3, block_decrypt/3, block_encrypt/4, block_decrypt/4]). -export([next_iv/2, next_iv/3]). @@ -305,12 +309,12 @@ rand_seed() -> rand:seed(rand_seed_s()). rand_seed_s() -> - {#{ type => crypto, + {#{ type => ?MODULE, max => infinity, - next => fun rand_plugin_next/1, - uniform => fun rand_plugin_uniform/1, - uniform_n => fun rand_plugin_uniform/2, - jump => fun rand_plugin_jump/1}, + next => fun ?MODULE:rand_plugin_next/1, + uniform => fun ?MODULE:rand_plugin_uniform/1, + uniform_n => fun ?MODULE:rand_plugin_uniform/2, + jump => fun ?MODULE:rand_plugin_jump/1}, no_seed}. rand_plugin_next(Seed) -> -- cgit v1.2.3 From c84e541b78cb9ee63a02db2240903ddd6131793a Mon Sep 17 00:00:00 2001 From: Guilherme Andrade Date: Wed, 22 Mar 2017 22:15:15 +0000 Subject: Attempt faster approach to strong random floats --- lib/crypto/src/crypto.erl | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/lib/crypto/src/crypto.erl b/lib/crypto/src/crypto.erl index 777b5cd1c6..d8bc13b537 100644 --- a/lib/crypto/src/crypto.erl +++ b/lib/crypto/src/crypto.erl @@ -49,6 +49,9 @@ %% This should correspond to the similar macro in crypto.c -define(MAX_BYTES_TO_NIF, 20000). %% Current value is: erlang:system_info(context_reductions) * 10 +%% Used by strong_rand_float/0 +-define(HALF_DBL_EPSILON, 1.1102230246251565e-16). % math:pow(2, -53) + %%-type ecdsa_digest_type() :: 'md5' | 'sha' | 'sha256' | 'sha384' | 'sha512'. -type crypto_integer() :: binary() | integer(). %%-type ec_named_curve() :: atom(). @@ -342,14 +345,8 @@ strong_rand_range(BinRange) when is_binary(BinRange) -> strong_rand_range_nif(_BinRange) -> ?nif_stub. strong_rand_float() -> - % This could be optimized by having its own NIF - Sign = 0, % positive - Exponent = 1023, % on the interval [1.0, 2.0[ - BinFraction = strong_rand_range(1 bsl 52), % the whole interval above - Fraction = bin_to_int(BinFraction), - <> = <>, - Value - 1.0. - + WholeRange = strong_rand_range(1 bsl 53), + ?HALF_DBL_EPSILON * bytes_to_integer(WholeRange). rand_uniform(From,To) when is_binary(From), is_binary(To) -> case rand_uniform_nif(From,To) of -- cgit v1.2.3 From e1a74e3077ca870520a748f29dd7c4b9115ce090 Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Mon, 3 Apr 2017 12:29:23 +0200 Subject: Clean up documentation and test cases --- lib/crypto/doc/src/crypto.xml | 35 +++++++++++++--------- lib/stdlib/doc/src/rand.xml | 43 +++++++++++++++++++------- lib/stdlib/src/rand.erl | 68 ++++++++++++++++++++++++++---------------- lib/stdlib/test/rand_SUITE.erl | 33 ++++++++++++++------ 4 files changed, 120 insertions(+), 59 deletions(-) diff --git a/lib/crypto/doc/src/crypto.xml b/lib/crypto/doc/src/crypto.xml index 30835a8447..552d95d7dc 100644 --- a/lib/crypto/doc/src/crypto.xml +++ b/lib/crypto/doc/src/crypto.xml @@ -732,14 +732,17 @@ rand_seed() -> rand:state() - Strong random number generation plugin state> - - Creates state object for random number generation, - in order to generate cryptographically strong random numbers - (based on OpenSSL's BN_rand_range), - and saves it on process dictionary before returning it as well. - See also rand:seed/1 - + Strong random number generation plugin state + +

+ Creates state object for + random number generation, + in order to generate cryptographically strong random numbers + (based on OpenSSL's BN_rand_range), + and saves it on process dictionary before returning it as well. + See also + rand:seed/1. +

Example

 _ = crypto:rand_seed(),
@@ -750,12 +753,16 @@ _FloatValue = rand:uniform().     % [0.0; 1.0[
rand_seed_s() -> rand:state() - Strong random number generation plugin state> - - Creates state object for random number generation, - in order to generate cryptographically strongly random numbers - (based on OpenSSL's BN_rand_range). - See also rand:seed_s/1 + Strong random number generation plugin state + +

+ Creates state object for + random number generation, + in order to generate cryptographically strongly random numbers + (based on OpenSSL's BN_rand_range). + See also + rand:seed_s/1. +

diff --git a/lib/stdlib/doc/src/rand.xml b/lib/stdlib/doc/src/rand.xml index e7a5fb7fab..2ddf3021ac 100644 --- a/lib/stdlib/doc/src/rand.xml +++ b/lib/stdlib/doc/src/rand.xml @@ -139,7 +139,19 @@ S0 = rand:seed_s(exsplus), - + + + + +

Algorithm specific internal state

+
+ + +

Algorithm specific internal state

+
+ + +

Algorithm specific internal state

@@ -147,8 +159,11 @@ S0 = rand:seed_s(exsplus), -

Algorithm-dependent state that can be printed or saved to - file.

+ +

+ Algorithm-dependent state that can be printed or saved to file. +

+
@@ -223,8 +238,11 @@ S0 = rand:seed_s(exsplus), Seed random number generator. -

Seeds random number generation with the specifed algorithm and - time-dependent data if AlgOrStateOrExpState is an algorithm.

+

+ Seeds random number generation with the specifed algorithm and + time-dependent data if AlgOrStateOrExpState + is an algorithm. +

Otherwise recreates the exported seed in the process dictionary, and returns the state. See also export_seed/0.

@@ -244,8 +262,11 @@ S0 = rand:seed_s(exsplus), Seed random number generator. -

Seeds random number generation with the specifed algorithm and - time-dependent data if AlgOrStateOrExpState is an algorithm.

+

+ Seeds random number generation with the specifed algorithm and + time-dependent data if AlgOrStateOrExpState + is an algorithm. +

Otherwise recreates the exported seed and returns the state. See also export_seed/0.

@@ -266,7 +287,7 @@ S0 = rand:seed_s(exsplus), Return a random float.

Returns a random float uniformly distributed in the value - range 0.0 < X < 1.0 and + range 0.0 =< X < 1.0 and updates the state in the process dictionary.

@@ -277,7 +298,7 @@ S0 = rand:seed_s(exsplus),

Returns, for a specified integer N >= 1, a random integer uniformly distributed in the value range - 1 <= X <= N and + 1 =< X =< N and updates the state in the process dictionary.

@@ -287,7 +308,7 @@ S0 = rand:seed_s(exsplus), Return a random float.

Returns, for a specified state, random float - uniformly distributed in the value range 0.0 < + uniformly distributed in the value range 0.0 =< X < 1.0 and a new state.

@@ -298,7 +319,7 @@ S0 = rand:seed_s(exsplus),

Returns, for a specified integer N >= 1 and a state, a random integer uniformly distributed in the value - range 1 <= X <= N and a + range 1 =< X =< N and a new state.

diff --git a/lib/stdlib/src/rand.erl b/lib/stdlib/src/rand.erl index 60da53cd2b..dfd102f9ef 100644 --- a/lib/stdlib/src/rand.erl +++ b/lib/stdlib/src/rand.erl @@ -45,22 +45,31 @@ %% ===================================================================== %% This depends on the algorithm handler function --type alg_seed() :: exs64_state() | exsplus_state() | exs1024_state() | term(). +-type alg_state() :: + exs64_state() | exsplus_state() | exs1024_state() | term(). %% This is the algorithm handler function within this module --type alg_handler() :: #{type := alg(), - max := integer() | infinity, - next := fun((alg_seed()) -> {uint64(), alg_seed()}), - uniform := fun((state()) -> {float(), state()}), - uniform_n := fun((pos_integer(), state()) -> {pos_integer(), state()}), - jump := fun((state()) -> state())}. +-type alg_handler() :: + #{type := alg(), + max := integer() | infinity, + next := + fun((alg_state()) -> {non_neg_integer(), alg_state()}), + uniform := + fun((state()) -> {float(), state()}), + uniform_n := + fun((pos_integer(), state()) -> {pos_integer(), state()}), + jump := + fun((state()) -> state())}. %% Algorithm state --type state() :: {alg_handler(), alg_seed()}. +-type state() :: {alg_handler(), alg_state()}. -type builtin_alg() :: exs64 | exsplus | exs1024. -type alg() :: builtin_alg() | atom(). --type export_state() :: {alg(), alg_seed()}. --export_type([builtin_alg/0, alg/0, alg_handler/0, alg_seed/0, state/0, export_state/0]). +-type export_state() :: {alg(), alg_state()}. +-export_type( + [builtin_alg/0, alg/0, alg_handler/0, alg_state/0, + state/0, export_state/0]). +-export_type([exs64_state/0, exsplus_state/0, exs1024_state/0]). %% ===================================================================== %% API @@ -74,7 +83,7 @@ export_seed() -> _ -> undefined end. --spec export_seed_s(state()) -> export_state(). +-spec export_seed_s(State :: state()) -> export_state(). export_seed_s({#{type:=Alg}, Seed}) -> {Alg, Seed}. %% seed(Alg) seeds RNG with runtime dependent values @@ -83,11 +92,15 @@ export_seed_s({#{type:=Alg}, Seed}) -> {Alg, Seed}. %% seed({Alg,Seed}) setup RNG with a previously exported seed %% and return the NEW state --spec seed(AlgOrStateOrExpState::builtin_alg() | state() | export_state()) -> state(). +-spec seed( + AlgOrStateOrExpState :: builtin_alg() | state() | export_state()) -> + state(). seed(Alg) -> seed_put(seed_s(Alg)). --spec seed_s(AlgOrStateOrExpState::builtin_alg() | state() | export_state()) -> state(). +-spec seed_s( + AlgOrStateOrExpState :: builtin_alg() | state() | export_state()) -> + state(). seed_s({AlgHandler, _Seed} = State) when is_map(AlgHandler) -> State; seed_s({Alg0, Seed}) -> @@ -101,11 +114,15 @@ seed_s(Alg) -> %% seed/2: seeds RNG with the algorithm and given values %% and returns the NEW state. --spec seed(Alg :: builtin_alg(), {integer(), integer(), integer()}) -> state(). +-spec seed( + Alg :: builtin_alg(), Seed :: {integer(), integer(), integer()}) -> + state(). seed(Alg0, S0) -> seed_put(seed_s(Alg0, S0)). --spec seed_s(Alg :: builtin_alg(), {integer(), integer(), integer()}) -> state(). +-spec seed_s( + Alg :: builtin_alg(), Seed :: {integer(), integer(), integer()}) -> + state(). seed_s(Alg0, S0 = {_, _, _}) -> {Alg, Seed} = mk_alg(Alg0), AS = Seed(S0), @@ -117,7 +134,7 @@ seed_s(Alg0, S0 = {_, _, _}) -> %% uniform/0: returns a random float X where 0.0 < X < 1.0, %% updating the state in the process dictionary. --spec uniform() -> X::float(). +-spec uniform() -> X :: float(). uniform() -> {X, Seed} = uniform_s(seed_get()), _ = seed_put(Seed), @@ -127,7 +144,7 @@ uniform() -> %% uniform/1 returns a random integer X where 1 =< X =< N, %% updating the state in the process dictionary. --spec uniform(N :: pos_integer()) -> X::pos_integer(). +-spec uniform(N :: pos_integer()) -> X :: pos_integer(). uniform(N) -> {X, Seed} = uniform_s(N, seed_get()), _ = seed_put(Seed), @@ -137,7 +154,7 @@ uniform(N) -> %% returns a random float X where 0.0 < X < 1.0, %% and a new state. --spec uniform_s(state()) -> {X::float(), NewS :: state()}. +-spec uniform_s(State :: state()) -> {X :: float(), NewState :: state()}. uniform_s(State = {#{uniform:=Uniform}, _}) -> Uniform(State). @@ -145,7 +162,8 @@ uniform_s(State = {#{uniform:=Uniform}, _}) -> %% uniform_s/2 returns a random integer X where 1 =< X =< N, %% and a new state. --spec uniform_s(N::pos_integer(), state()) -> {X::pos_integer(), NewS::state()}. +-spec uniform_s(N :: pos_integer(), State :: state()) -> + {X :: pos_integer(), NewState :: state()}. uniform_s(N, State = {#{uniform_n:=Uniform, max:=Max}, _}) when 0 < N, N =< Max -> Uniform(N, State); @@ -159,7 +177,7 @@ uniform_s(N, State0 = {#{uniform:=Uniform}, _}) %% after a large number of call defined for each algorithm. %% The large number is algorithm dependent. --spec jump(state()) -> NewS :: state(). +-spec jump(state()) -> NewState :: state(). jump(State = {#{jump:=Jump}, _}) -> Jump(State). @@ -168,7 +186,7 @@ jump(State = {#{jump:=Jump}, _}) -> %% and write back the new value to the internal state, %% then returns the new value. --spec jump() -> NewS :: state(). +-spec jump() -> NewState :: state(). jump() -> seed_put(jump(seed_get())). @@ -186,7 +204,7 @@ normal() -> %% The Ziggurat Method for generating random variables - Marsaglia and Tsang %% Paper and reference code: http://www.jstatsoft.org/v05/i08/ --spec normal_s(state()) -> {float(), NewS :: state()}. +-spec normal_s(State :: state()) -> {float(), NewState :: state()}. normal_s(State0) -> {Sign, R, State} = get_52(State0), Idx = R band 16#FF, @@ -249,7 +267,7 @@ mk_alg(exs1024) -> %% Reference URL: http://xorshift.di.unimi.it/ %% ===================================================================== --type exs64_state() :: uint64(). +-opaque exs64_state() :: uint64(). exs64_seed({A1, A2, A3}) -> {V1, _} = exs64_next(((A1 band ?UINT32MASK) * 4294967197 + 1)), @@ -284,7 +302,7 @@ exs64_jump(_) -> %% Modification of the original Xorshift128+ algorithm to 116 %% by Sebastiano Vigna, a lot of thanks for his help and work. %% ===================================================================== --type exsplus_state() :: nonempty_improper_list(uint58(), uint58()). +-opaque exsplus_state() :: nonempty_improper_list(uint58(), uint58()). -dialyzer({no_improper_lists, exsplus_seed/1}). @@ -353,7 +371,7 @@ exsplus_jump(S, [AS0|AS1], J, N) -> %% Reference URL: http://xorshift.di.unimi.it/ %% ===================================================================== --type exs1024_state() :: {list(uint64()), list(uint64())}. +-opaque exs1024_state() :: {list(uint64()), list(uint64())}. exs1024_seed({A1, A2, A3}) -> B1 = (((A1 band ?UINT21MASK) + 1) * 2097131) band ?UINT21MASK, diff --git a/lib/stdlib/test/rand_SUITE.erl b/lib/stdlib/test/rand_SUITE.erl index fe5eaccda5..098eefeb61 100644 --- a/lib/stdlib/test/rand_SUITE.erl +++ b/lib/stdlib/test/rand_SUITE.erl @@ -356,14 +356,23 @@ basic_normal_1(0, {#{type:=Alg}, _}, Sum, SumSq) -> %% Test that the user can write algorithms. plugin(Config) when is_list(Config) -> - _ = lists:foldl(fun(_, S0) -> - {V1, S1} = rand:uniform_s(10000, S0), - true = is_integer(V1), - {V2, S2} = rand:uniform_s(S1), - true = is_float(V2), - S2 - end, crypto_seed(), lists:seq(1, 200)), - ok. + try crypto:strong_rand_bytes(1) of + <<_>> -> + _ = lists:foldl( + fun(_, S0) -> + {V1, S1} = rand:uniform_s(10000, S0), + true = is_integer(V1), + {V2, S2} = rand:uniform_s(S1), + true = is_float(V2), + S2 + end, crypto_seed(), lists:seq(1, 200)), + ok + catch + error:low_entropy -> + {skip,low_entropy}; + error:undef -> + {skip,no_crypto} + end. %% Test implementation crypto_seed() -> @@ -397,7 +406,13 @@ crypto_uniform_n(N, State0) -> measure(Suite) when is_atom(Suite) -> []; measure(_Config) -> ct:timetrap({minutes,15}), %% valgrind needs a lot of time - Algos = [crypto64|algs()], + Algos = + try crypto:strong_rand_bytes(1) of + <<_>> -> [crypto64] + catch + error:low_entropy -> []; + error:undef -> [] + end ++ algs(), io:format("RNG uniform integer performance~n",[]), _ = measure_1(random, fun(State) -> {int, random:uniform_s(10000, State)} end), _ = [measure_1(Algo, fun(State) -> {int, rand:uniform_s(10000, State)} end) || Algo <- Algos], -- cgit v1.2.3