Skip to content

Commit

Permalink
Use long period counter for crypto_aes
Browse files Browse the repository at this point in the history
  • Loading branch information
RaimoNiskanen committed Jul 4, 2018
1 parent a3f615d commit 9dfe9cb
Show file tree
Hide file tree
Showing 4 changed files with 245 additions and 76 deletions.
115 changes: 102 additions & 13 deletions lib/crypto/doc/src/crypto.xml
Original file line number Diff line number Diff line change
Expand Up @@ -856,7 +856,8 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
<p>
Creates state object for
<seealso marker="stdlib:rand">random number generation</seealso>,
in order to generate cryptographically strong random numbers.
in order to generate cryptographically strong random numbers,
and saves it in the process dictionary before returning it as well.
See also
<seealso marker="stdlib:rand#seed-1">rand:seed/1</seealso> and
<seealso marker="#rand_seed_alg_s-1">rand_seed_alg_s/1</seealso>.
Expand All @@ -867,12 +868,6 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
may throw exception <c>low_entropy</c> in case the random generator
failed due to lack of secure "randomness".
</p>
<p>
The cache size can be changed from its default value using the
<seealso marker="crypto_app">
crypto app's
</seealso> configuration parameter <c>rand_cache_size</c>.
</p>
<p><em>Example</em></p>
<pre>
_ = crypto:rand_seed_alg(crypto_cache),
Expand All @@ -881,6 +876,34 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
</desc>
</func>

<func>
<name>rand_seed_alg(Alg, Seed) -> rand:state()</name>
<fsummary>Strong random number generation plugin state</fsummary>
<type>
<v>Alg = crypto_aes</v>
</type>
<desc>
<marker id="rand_seed_alg-2" />
<p>
Creates a state object for
<seealso marker="stdlib:rand">random number generation</seealso>,
in order to generate cryptographically unpredictable random numbers,
and saves it in the process dictionary before returning it as well.
See also
<seealso marker="#rand_seed_alg_s-2">rand_seed_alg_s/2</seealso>.
</p>
<p><em>Example</em></p>
<pre>
_ = crypto:rand_seed_alg(crypto_aes, "my seed"),
IntegerValue = rand:uniform(42), % [1; 42]
FloatValue = rand:uniform(), % [0.0; 1.0[
_ = crypto:rand_seed_alg(crypto_aes, "my seed"),
IntegerValue = rand:uniform(42), % Same values
FloatValue = rand:uniform(). % again
</pre>
</desc>
</func>

<func>
<name>rand_seed_alg_s(Alg) -> rand:state()</name>
<fsummary>Strong random number generation plugin state</fsummary>
Expand All @@ -906,18 +929,18 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
and caches it for speed using an internal word size
of 56 bits that makes calculations fast on 64 bit machines.
</p>
<p>
When using the state object from this function the
<seealso marker="stdlib:rand">rand</seealso> functions using it
may throw exception <c>low_entropy</c> in case the random generator
failed due to lack of secure "randomness".
</p>
<p>
The cache size can be changed from its default value using the
<seealso marker="crypto_app">
crypto app's
</seealso> configuration parameter <c>rand_cache_size</c>.
</p>
<p>
When using the state object from this function the
<seealso marker="stdlib:rand">rand</seealso> functions using it
may throw exception <c>low_entropy</c> in case the random generator
failed due to lack of secure "randomness".
</p>
<note>
<p>
The state returned from this function can not be used
Expand All @@ -939,6 +962,72 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
</desc>
</func>

<func>
<name>rand_seed_alg_s(Alg, Seed) -> rand:state()</name>
<fsummary>Strong random number generation plugin state</fsummary>
<type>
<v>Alg = crypto_aes</v>
</type>
<desc>
<marker id="rand_seed_alg_s-2" />
<p>
Creates a state object for
<seealso marker="stdlib:rand">random number generation</seealso>,
in order to generate cryptographically unpredictable random numbers.
See also
<seealso marker="#rand_seed_alg-1">rand_seed_alg/1</seealso>.
</p>
<p>
To get a long period the Xoroshiro928 generator from the
<seealso marker="stdlib:rand">rand</seealso>
module is used as a counter (with period 2^928 - 1)
and the generator states are scrambled through AES
to create 58-bit pseudo random values.
</p>
<p>
The result should be statistically completely unpredictable
random values, since the scrambling is cryptographically strong
and the period is ridiculously long. But the generated numbers
are not to be regarded as cryptographically strong since
there is no re-keying schedule.
</p>
<list type="bulleted">
<item>
<p>
If you need cryptographically strong random numbers use
<seealso marker="#rand_seed_alg_s-1">rand_seed_alg_s/1</seealso>
with <c>Alg =:= crypto</c> or <c>Alg =:= crypto_cache</c>.
</p>
</item>
<item>
<p>
If you need to be able to repeat the sequence use this function.
</p>
</item>
<item>
<p>
If you do not need the statistical quality of this function,
there are faster algorithms in the
<seealso marker="stdlib:rand">rand</seealso>
module.
</p>
</item>
</list>
<p>
Thanks to the used generator the state object supports the
<seealso marker="stdlib:rand#jump-0"><c>rand:jump/0,1</c></seealso>
function with distance 2^512.
</p>
<p>
Numbers are generated in batches and cached for speed reasons.
The cache size can be changed from its default value using the
<seealso marker="crypto_app">
crypto app's
</seealso> configuration parameter <c>rand_cache_size</c>.
</p>
</desc>
</func>

<func>
<name>stream_init(Type, Key) -> State</name>
<fsummary></fsummary>
Expand Down
147 changes: 98 additions & 49 deletions lib/crypto/src/crypto.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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([rand_seed/0, rand_seed_alg/1]).
-export([rand_seed_s/0, rand_seed_alg_s/1]).
-export([rand_seed/0, rand_seed_alg/1, rand_seed_alg/2]).
-export([rand_seed_s/0, rand_seed_alg_s/1, rand_seed_alg_s/2]).
-export([rand_plugin_next/1]).
-export([rand_plugin_aes_next/1, rand_plugin_aes_jump/1]).
-export([rand_plugin_uniform/1]).
Expand Down Expand Up @@ -64,7 +64,9 @@


%% Private. For tests.
-export([packed_openssl_version/4, engine_methods_convert_to_bitmask/2, get_test_engine/0]).
-export([packed_openssl_version/4, engine_methods_convert_to_bitmask/2,
get_test_engine/0]).
-export([rand_plugin_aes_jump_2pow20/1]).

-deprecated({rand_uniform, 2, next_major_release}).

Expand Down Expand Up @@ -332,9 +334,15 @@ stream_decrypt(State, Data0) ->
-spec rand_seed_alg(Alg :: atom()) ->
{rand:alg_handler(),
atom() | rand_cache_seed()}.
-spec rand_seed_alg(Alg :: atom(), Seed :: term()) ->
{rand:alg_handler(),
atom() | rand_cache_seed()}.
-spec rand_seed_alg_s(Alg :: atom()) ->
{rand:alg_handler(),
atom() | rand_cache_seed()}.
-spec rand_seed_alg_s(Alg :: atom(), Seed :: term()) ->
{rand:alg_handler(),
atom() | rand_cache_seed()}.
-spec rand_uniform(crypto_integer(), crypto_integer()) ->
crypto_integer().

Expand All @@ -355,35 +363,52 @@ rand_seed_s() ->
rand_seed_alg(Alg) ->
rand:seed(rand_seed_alg_s(Alg)).

rand_seed_alg(Alg, Seed) ->
rand:seed(rand_seed_alg_s(Alg, Seed)).

-define(CRYPTO_CACHE_BITS, 56).
-define(CRYPTO_AES_BITS, 58).
rand_seed_alg_s(?MODULE) ->
{#{ type => ?MODULE,
bits => 64,
next => fun ?MODULE:rand_plugin_next/1,
uniform => fun ?MODULE:rand_plugin_uniform/1,
uniform_n => fun ?MODULE:rand_plugin_uniform/2},
no_seed};
rand_seed_alg_s(crypto_cache) ->
rand_seed_alg_s({AlgHandler, _AlgState} = State) when is_map(AlgHandler) ->
State;
rand_seed_alg_s({Alg, AlgState}) when is_atom(Alg) ->
{mk_alg_handler(Alg),AlgState};
rand_seed_alg_s(Alg) when is_atom(Alg) ->
{mk_alg_handler(Alg),mk_alg_state(Alg)}.
%%
rand_seed_alg_s(Alg, Seed) when is_atom(Alg) ->
{mk_alg_handler(Alg),mk_alg_state({Alg,Seed})}.

mk_alg_handler(?MODULE = Alg) ->
#{ type => Alg,
bits => 64,
next => fun ?MODULE:rand_plugin_next/1,
uniform => fun ?MODULE:rand_plugin_uniform/1,
uniform_n => fun ?MODULE:rand_plugin_uniform/2};
mk_alg_handler(crypto_cache = Alg) ->
#{ type => Alg,
bits => ?CRYPTO_CACHE_BITS,
next => fun ?MODULE:rand_cache_plugin_next/1};
mk_alg_handler(crypto_aes = Alg) ->
#{ type => Alg,
bits => ?CRYPTO_AES_BITS,
next => fun ?MODULE:rand_plugin_aes_next/1,
jump => fun ?MODULE:rand_plugin_aes_jump/1}.

mk_alg_state(?MODULE) ->
no_seed;
mk_alg_state(crypto_cache) ->
CacheBits = ?CRYPTO_CACHE_BITS,
BytesPerWord = (CacheBits + 7) div 8,
GenBytes =
((rand_cache_size() + (2*BytesPerWord - 1)) div BytesPerWord)
* BytesPerWord,
{#{ type => crypto_cache,
bits => CacheBits,
next => fun ?MODULE:rand_cache_plugin_next/1},
{CacheBits, GenBytes, <<>>}};
rand_seed_alg_s({crypto_aes,Seed}) ->
{CacheBits, GenBytes, <<>>};
mk_alg_state({crypto_aes,Seed}) ->
%% 16 byte words (128 bit crypto blocks)
GenWords = (rand_cache_size() + 31) div 16,
Key = crypto:hash(sha256, Seed),
NN = [0,0,0,0],
{#{ type => crypto_aes,
bits => ?CRYPTO_AES_BITS,
next => fun ?MODULE:rand_plugin_aes_next/1,
jump => fun ?MODULE:rand_plugin_aes_jump/1},
{Key,GenWords,NN}}.
{F,Count} = longcount_seed(Seed),
{Key,GenWords,F,Count}.

rand_cache_size() ->
DefaultCacheSize = 1024,
Expand Down Expand Up @@ -424,44 +449,68 @@ rand_cache_plugin_next({CacheBits, GenBytes, Cache}) ->
-dialyzer({no_improper_lists, rand_plugin_aes_next/1}).
rand_plugin_aes_next([V|Cache]) ->
{V,Cache};
rand_plugin_aes_next({Key,GenWords,NN}) ->
{Cleartext,NewNN} = aes_cleartext(<<>>, NN, GenWords),
rand_plugin_aes_next({Key,GenWords,F,Count}) ->
rand_plugin_aes_next(Key, GenWords, F, Count);
rand_plugin_aes_next({Key,GenWords,F,_JumpBase,Count}) ->
rand_plugin_aes_next(Key, GenWords, F, Count).
%%
rand_plugin_aes_next(Key, GenWords, F, Count) ->
{Cleartext,NewCount} = aes_cleartext(<<>>, F, Count, GenWords),
Encrypted = crypto:block_encrypt(aes_ecb, Key, Cleartext),
[V|Cache] = aes_cache(Encrypted, {Key,GenWords,NewNN}),
[V|Cache] = aes_cache(Encrypted, {Key,GenWords,F,Count,NewCount}),
{V,Cache}.

%% A jump advances the counter 2^64 steps; the the number
%% of cached random values has to be subtracted
%% for the jump to be correct.
-dialyzer({no_improper_lists, rand_plugin_aes_jump/1}).
%% A jump advances the counter 2^512 steps; the jump function
%% is applied to the jump base and then the number of used
%% numbers from the cache has to be wasted for the jump to be correct
%%
rand_plugin_aes_jump({#{type := crypto_aes} = Alg, Cache}) ->
rand_plugin_aes_jump(Alg, Cache, 0).
{Alg,rand_plugin_aes_jump(fun longcount_jump/1, 0, Cache)}.
%% Count cached words and subtract their number from jump
-dialyzer({no_improper_lists, rand_plugin_aes_jump/3}).
rand_plugin_aes_jump(Alg, [_|Cache], J) ->
rand_plugin_aes_jump(Alg, Cache, J + 1);
rand_plugin_aes_jump(Alg, {Key,GenWords,NN}, J) ->
{Alg, {Key,GenWords,long32_add(NN, (1 bsl 64) - J)}}.
rand_plugin_aes_jump(Jump, J, [_|Cache]) ->
rand_plugin_aes_jump(Jump, J + 1, Cache);
rand_plugin_aes_jump(Jump, J, {Key,GenWords,F,JumpBase, _Count}) ->
rand_plugin_aes_jump(Jump, GenWords - J, Key, GenWords, F, JumpBase);
rand_plugin_aes_jump(Jump, 0, {Key,GenWords,F,JumpBase}) ->
rand_plugin_aes_jump(Jump, 0, Key, GenWords, F, JumpBase).
%%
rand_plugin_aes_jump(Jump, Skip, Key, GenWords, F, JumpBase) ->
Count = longcount_next_count(Skip, Jump(JumpBase)),
{Key,GenWords,F,Count}.

rand_plugin_aes_jump_2pow20(Cache) ->
rand_plugin_aes_jump(fun longcount_jump_2pow20/1, 0, Cache).


longcount_seed(Seed) ->
<<X:64, _:6, F:12, S2:58, S1:58, S0:58>> =
crypto:hash(sha256, [Seed,<<"Xoroshiro928">>]),
{F,rand:exro928_seed([S0,S1,S2|rand:seed58(13, X)])}.

longcount_next_count(0, Count) ->
Count;
longcount_next_count(N, Count) ->
longcount_next_count(N - 1, rand:exro928_next_state(Count)).

longcount_next(Count) ->
rand:exro928_next(Count).

longcount_jump(Count) ->
rand:exro928_jump_2pow512(Count).

longcount_jump_2pow20(Count) ->
rand:exro928_jump_2pow20(Count).

long32_add([], _) -> [];
long32_add([X|N], Cy) ->
Y = X + Cy,
if
Y < 0; 1 bsl 32 =< Y ->
[(Y band ((1 bsl 32) - 1))|long32_add(N, Y bsr 32)];
true ->
[Y|N]
end.

%% Build binary with counter values to cache
aes_cleartext(Cleartext, NN, 0) ->
{Cleartext,NN};
aes_cleartext(Cleartext, [A,B,C,D] = NN, GenWords) ->
aes_cleartext(Cleartext, _F, Count, 0) ->
{Cleartext,Count};
aes_cleartext(Cleartext, F, Count, GenWords) ->
{{S0,S1}, NewCount} = longcount_next(Count),
aes_cleartext(
<<Cleartext/binary, D:32, C:32, B:32, A:32>>,
long32_add(NN, 1),
GenWords - 1).
<<Cleartext/binary, F:12, S1:58, S0:58>>,
F, NewCount, GenWords - 1).

%% Parse and cache encrypted counter values aka random numbers
-dialyzer({no_improper_lists, aes_cache/2}).
Expand Down
Loading

0 comments on commit 9dfe9cb

Please sign in to comment.