%% MIT License %% Copyright (c) 2017 Knoxen %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal %% in the Software without restriction, including without limitation the rights %% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell %% copies of the Software, and to permit persons to whom the Software is %% furnished to do so, subject to the following conditions: %% The above copyright notice and this permission notice shall be included in all %% copies or substantial portions of the Software. %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE %% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, %% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %% SOFTWARE. %% %%================================================================================================== %% %% @doc %% Efficiently generate cryptographically strong random strings of specified entropy from various %% character sets. %% %% @author Paul Rogers %% %%================================================================================================== -module(entropy_string). -author("paul@knoxen.com"). -define(BITS_PER_BYTE, 8). -define(SMALL_ID_BITS, 29). -define(MEDIUM_ID_BITS, 69). -define(LARGE_ID_BITS, 99). -define(SESSION_ID_BITS, 128). -define(TOKEN_BITS, 256). %%================================================================================================== %% API %%================================================================================================== -export([charset/1 ,small_id/0 ,small_id/1 ,medium_id/0 ,medium_id/1 ,large_id/0 ,large_id/1 ,session_id/0 ,session_id/1 ,token/0 ,token/1 ,random_string/1 ,random_string/2 ,random_string/3 ,ten_p/1 ,bits/2 ,bits_per_char/1 ,bytes_needed/2 ,valid_charset/1 ,valid_bytes/3 ]). %%================================================================================================== %% Types %%================================================================================================== -type charset() :: charset64 | charset32 | charset16 | charset8 | charset4 | charset2 | binary(). -type reason() :: binary(). %%================================================================================================== %% Public Functions %%================================================================================================== %%-------------------------------------------------------------------------------------------------- %% @doc Predefined CharSets %% %%
charset64 : ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_charset32 : 2346789bdfghjmnpqrtBDFGHJLMNPQRTcharset16 : 0123456789abcdefcharset8 : 01234567charset4 : ATCGcharset2 : 01charset32.
%%
%% Returns random string with a 1 in a million chance of repeat in 30 strings using
%% charset32.
-spec small_id() -> String when
String :: binary().
%%--------------------------------------------------------------------------------------------------
small_id() ->
small_id(charset32).
%%--------------------------------------------------------------------------------------------------
%% @doc Small ID using CharSet
%%
%% Returns random string with a 1 in a million chance of repeat in 30 strings
-spec small_id(CharSet) -> String when
CharSet :: charset(),
String :: binary() | {error, reason()}.
%%--------------------------------------------------------------------------------------------------
small_id(CharSet) ->
random_string(?SMALL_ID_BITS, CharSet).
%%--------------------------------------------------------------------------------------------------
%% @doc Medium ID using charset32.
%%
%% Returns random string with a 1 in a billion chance of repeat in a million strings using
%% charset32.
-spec medium_id() -> String when
String :: binary().
%%--------------------------------------------------------------------------------------------------
medium_id() ->
medium_id(charset32).
%%--------------------------------------------------------------------------------------------------
%% @doc Medium ID using CharSet
%%
%% Returns random string with a 1 in a billion chance of repeat in a million strings.
-spec medium_id(CharSet) -> String when
CharSet :: charset(),
String :: binary() | {error, reason()}.
%%--------------------------------------------------------------------------------------------------
medium_id(CharSet) ->
random_string(?MEDIUM_ID_BITS, CharSet).
%%--------------------------------------------------------------------------------------------------
%% @doc Large ID using charset32.
%%
%% Returns random string with a 1 in a trillion chance of repeat in a billion strings using
%% charset32.
-spec large_id() -> String when
String :: binary().
%%--------------------------------------------------------------------------------------------------
large_id() ->
large_id(charset32).
%%--------------------------------------------------------------------------------------------------
%% @doc Large ID using CharSet
%%
%% Returns random string with a 1 in a trillion chance of repeat in a billion strings.
-spec large_id(CharSet) -> String when
CharSet :: charset(),
String :: binary() | {error, reason()}.
%%--------------------------------------------------------------------------------------------------
large_id(CharSet) ->
random_string(?LARGE_ID_BITS, CharSet).
%%--------------------------------------------------------------------------------------------------
%% @doc Session ID using charset32.
%%
%% Returns random string suitable for 128-bit OWASP Session ID using charset32.
-spec session_id() -> String when
String :: binary().
%%--------------------------------------------------------------------------------------------------
session_id() ->
session_id(charset32).
%%--------------------------------------------------------------------------------------------------
%% @doc Session ID using CharSet
%%
%% Returns random string suitable for 128-bit OWASP Session ID.
-spec session_id(CharSet) -> String when
CharSet :: charset(),
String :: binary() | {error, reason()}.
%%--------------------------------------------------------------------------------------------------
session_id(CharSet) ->
random_string(128, CharSet).
%%--------------------------------------------------------------------------------------------------
%% @doc Token using charset32.
%%
%% Returns random string with 256 bits of entropy using charset32
%% characters.
-spec token() -> String when
String :: binary().
%%--------------------------------------------------------------------------------------------------
token() ->
token(charset32).
%%--------------------------------------------------------------------------------------------------
%% @doc Token using CharSet
%%
%% Returns random string with 256 bits of entropy.
-spec token(CharSet) -> String when
CharSet :: charset(),
String :: binary() | {error, reason()}.
%%--------------------------------------------------------------------------------------------------
token(CharSet) ->
random_string(256, CharSet).
%%==================================================================================================
%%
%% random_string(bits)
%%
%%==================================================================================================
%%--------------------------------------------------------------------------------------------------
%% @doc String of entropy Bits using charset32.
%%
-spec random_string(Bits) -> String when
Bits :: number(),
String :: binary().
%%--------------------------------------------------------------------------------------------------
random_string(Bits) ->
random_string(Bits, charset32).
%%==================================================================================================
%%
%% random_string(bits, charset)
%%
%%==================================================================================================
%%--------------------------------------------------------------------------------------------------
%% @doc String of entropy Bits using CharSet
%%
-spec random_string(Bits, CharSet) -> String when
Bits :: number(),
CharSet :: charset(),
String :: binary() | {error, reason()}.
%%--------------------------------------------------------------------------------------------------
random_string(Bits, CharSet) when
CharSet =:= charset64;
CharSet =:= charset32;
CharSet =:= charset16;
CharSet =:= charset8;
CharSet =:= charset4;
CharSet =:= charset2 ->
es_string(Bits, CharSet);
random_string(Bits, CharSet) ->
case valid_charset(CharSet) of
true ->
es_string(Bits, CharSet);
Error ->
Error
end.
%%--------------------------------------------------------------------------------------------------
%% Generate Bytes and forward
%%--------------------------------------------------------------------------------------------------
es_string(Bits, CharSet) ->
ByteCount = bytes_needed(Bits, CharSet),
Bytes = crypto:strong_rand_bytes(ByteCount),
es_string_bytes(Bits, CharSet, Bytes).
%%==================================================================================================
%%
%% random_string(bits, charset, bytes)
%%
%%==================================================================================================
%%--------------------------------------------------------------------------------------------------
%% @doc String of entropy Bits using CharSet and Bytes
%%
-spec random_string(Bits, CharSet, Bytes) -> String when
Bits :: integer(),
CharSet :: charset(),
Bytes :: binary(),
String :: binary() | {error, reason()}.
%%--------------------------------------------------------------------------------------------------
random_string(Bits, CharSet, Bytes) when
CharSet =:= charset64;
CharSet =:= charset32;
CharSet =:= charset16;
CharSet =:= charset8;
CharSet =:= charset4;
CharSet =:= charset2 ->
es_string(Bits, CharSet, Bytes);
random_string(Bits, CharSet, Bytes) ->
case valid_charset(CharSet) of
true -> es_string(Bits, CharSet, Bytes);
Error -> Error
end.
%%--------------------------------------------------------------------------------------------------
%% Validate Bytes and forward
%%--------------------------------------------------------------------------------------------------
es_string(Bits, CharSet, Bytes) ->
case valid_bytes(Bits, CharSet, Bytes) of
true -> es_string_bytes(Bits, CharSet, Bytes);
Error -> Error
end.
%%--------------------------------------------------------------------------------------------------
%% Calc number of characters and forward
%%--------------------------------------------------------------------------------------------------
es_string_bytes(Bits, CharSet, Bytes) when
CharSet =:= charset64;
CharSet =:= charset32;
CharSet =:= charset16;
CharSet =:= charset8;
CharSet =:= charset4;
CharSet =:= charset2 ->
es_string_bytes(Bits, charset(CharSet), Bytes);
es_string_bytes(Bits, CharSet, Bytes) ->
BitsPerChar = bits_per_char(CharSet),
NdxFn = ndx_fn(CharSet),
CharCount = ceil(Bits / BitsPerChar),
es_string_count(CharCount, NdxFn, CharSet, Bytes, <<>>).
%%--------------------------------------------------------------------------------------------------
%% Tail-recursive string generator
%%--------------------------------------------------------------------------------------------------
es_string_count(0, _NdxFn, _CharSet, _Bytes, Chars) ->
Chars;
es_string_count(CharCount, NdxFn, CharSet, Bytes, Chars) ->
Slice = CharCount - 1,
Ndx = NdxFn(Slice, Bytes),
Char = binary:part(CharSet, Ndx, 1),
es_string_count(Slice, NdxFn, CharSet, Bytes, <