Skip to content

Commit 595b476

Browse files
authored
Merge pull request #110 from fdie/pbkdf2
use the crypto library native call `pbkdf2_hmac` if available
2 parents d704e43 + ee0eb35 commit 595b476

File tree

2 files changed

+87
-4
lines changed

2 files changed

+87
-4
lines changed

rebar.config

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
{erl_opts, [debug_info, {src_dirs, ["asn1", "src"]},
2323
nowarn_export_all,
2424
{platform_define, "^(R|1|2[012])", 'USE_OLD_CRYPTO_HMAC'},
25+
{platform_define, "^(R|1|2[0123]|24.[01])", 'NO_PBKDF2_HMAC'},
2526
{platform_define, "^(R|1|2[01])", 'USE_GETHOSTBYNAME'},
2627
{platform_define, "^(R|1|2[0123]|24\.[012])", 'USE_ADDRPORTCONNECT'},
2728
{i, "include"}]}.

src/scram.erl

Lines changed: 86 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
server_signature/3, client_signature/3, client_key/2,
2626
client_key_xor/2]).
2727

28+
-compile({nowarn_unused_function, [{hi_legacy,4}, {hi_legacy_round,4}]}).
29+
2830
-type algo() :: sha | sha256 | sha512.
2931
-spec salted_password(algo(), binary(), binary(), non_neg_integer()) -> binary().
3032

@@ -60,15 +62,29 @@ client_key_xor(ClientProof, ClientSignature) ->
6062
server_signature(Algo, ServerKey, AuthMessage) ->
6163
sha_mac(Algo, ServerKey, AuthMessage).
6264

65+
-ifdef(NO_PBKDF2_HMAC).
6366
hi(Algo, Password, Salt, IterationCount) ->
67+
hi_legacy(Algo, Password, Salt, IterationCount).
68+
-else.
69+
hi(_Algo, _Password, _Salt, 0) ->
70+
<<>>;
71+
hi(sha, Password, Salt, IterationCount) ->
72+
crypto:pbkdf2_hmac(sha, Password, Salt, IterationCount, 20);
73+
hi(sha256, Password, Salt, IterationCount) ->
74+
crypto:pbkdf2_hmac(sha256, Password, Salt, IterationCount, 32);
75+
hi(sha512, Password, Salt, IterationCount) ->
76+
crypto:pbkdf2_hmac(sha512, Password, Salt, IterationCount, 64).
77+
-endif.
78+
79+
hi_legacy(Algo, Password, Salt, IterationCount) ->
6480
U1 = sha_mac(Algo, Password, <<Salt/binary, 0, 0, 0, 1>>),
65-
crypto:exor(U1, hi_round(Algo, Password, U1, IterationCount - 1)).
81+
crypto:exor(U1, hi_legacy_round(Algo, Password, U1, IterationCount - 1)).
6682

67-
hi_round(Algo, Password, UPrev, 1) ->
83+
hi_legacy_round(Algo, Password, UPrev, 1) ->
6884
sha_mac(Algo, Password, UPrev);
69-
hi_round(Algo, Password, UPrev, IterationCount) ->
85+
hi_legacy_round(Algo, Password, UPrev, IterationCount) ->
7086
U = sha_mac(Algo, Password, UPrev),
71-
crypto:exor(U, hi_round(Algo, Password, U, IterationCount - 1)).
87+
crypto:exor(U, hi_legacy_round(Algo, Password, U, IterationCount - 1)).
7288

7389
-ifdef(USE_OLD_CRYPTO_HMAC).
7490
sha_mac(Algo, Key, Data) ->
@@ -77,3 +93,69 @@ sha_mac(Algo, Key, Data) ->
7793
sha_mac(Algo, Key, Data) ->
7894
crypto:mac(hmac, Algo, Key, Data).
7995
-endif.
96+
97+
-ifdef(EUNIT).
98+
-include_lib("eunit/include/eunit.hrl").
99+
100+
scram_algo_test_() -> [
101+
{<<"scram_algo_", (atom_to_binary(Algo, latin1))/binary, "_test">>,
102+
fun() ->
103+
Password = <<"pencil">>,
104+
Salt = base64:decode(<<"QSXCR+Q6sek8bf92">>),
105+
Iterations = Iterations,
106+
SaltedPassword = salted_password(Algo, Password, Salt, Iterations),
107+
?assertEqual(DkLen, byte_size(SaltedPassword)),
108+
ClientKey = client_key(Algo, SaltedPassword),
109+
?assertEqual(DkLen, byte_size(ClientKey)),
110+
StoredKey = stored_key(Algo, ClientKey),
111+
?assertEqual(DkLen, byte_size(StoredKey)),
112+
ServerKey = server_key(Algo, SaltedPassword),
113+
?assertEqual(DkLen, byte_size(ServerKey)),
114+
AuthMessage = <<"p,y,t,h,o,n,r,,">>,
115+
ServerSig = server_signature(Algo, ServerKey, AuthMessage),
116+
?assertEqual(DkLen, byte_size(ServerSig)),
117+
ClientSig = client_signature(Algo, StoredKey, AuthMessage),
118+
?assertEqual(DkLen, byte_size(ClientSig)),
119+
?assertEqual(ServerKey, crypto:mac(hmac, Algo, SaltedPassword, <<"Server Key">>)),
120+
?assertEqual(StoredKey, crypto:hash(Algo, client_key(Algo, SaltedPassword)))
121+
end
122+
} || {Algo, DkLen, Iterations} <- [{sha, 20, 4096}, {sha256, 32, 4096}, {sha512, 64, 10000}]].
123+
124+
client_key_xor_test() ->
125+
Key1 = <<1,2,3,4,5,6,7,8,9,10>>,
126+
Key2 = <<10,9,8,7,6,5,4,3,2,1>>,
127+
Result = client_key_xor(Key1, Key2),
128+
?assertEqual(<<11,11,11,3,3,3,3,11,11,11>>, Result),
129+
?assertEqual(Key1, client_key_xor(Result, Key2)).
130+
131+
scram_zero_iterations_test() ->
132+
Password = <<"test">>,
133+
Salt = <<"salt">>,
134+
?assertEqual(<<>>, salted_password(sha, Password, Salt, 0)),
135+
?assertEqual(<<>>, salted_password(sha256, Password, Salt, 0)),
136+
?assertEqual(<<>>, salted_password(sha512, Password, Salt, 0)).
137+
138+
-ifndef(NO_PBKDF2_HMAC).
139+
pbkdf2_hmac_vs_hi_legacy_test_() -> [
140+
{timeout, 30,
141+
{<<"scram_hi_", (atom_to_binary(Algo, latin1))/binary, "_test">>,
142+
fun() ->
143+
lists:foreach(
144+
fun(_) ->
145+
Password = << <<(rand:uniform(255))>> || _ <- lists:seq(1, rand:uniform(128)) >>,
146+
Salt = << <<(rand:uniform(255))>> || _ <- lists:seq(1, rand:uniform(64)) >>,
147+
Iterations = 1 + rand:uniform(10000),
148+
SaltedPassword1 = hi(Algo, Password, Salt, Iterations),
149+
SaltedPassword2 = hi_legacy(Algo, Password, Salt, Iterations),
150+
?assertEqual(byte_size(SaltedPassword1), DkLen),
151+
?assertEqual(SaltedPassword1, SaltedPassword2)
152+
end,
153+
lists:seq(1, 500)
154+
)
155+
end
156+
}
157+
} || {Algo, DkLen} <- [{sha, 20}, {sha256, 32}, {sha512, 64}]
158+
].
159+
-endif.
160+
161+
-endif.

0 commit comments

Comments
 (0)