diff --git a/src/pbkdf2.erl b/src/pbkdf2.erl new file mode 100644 index 000000000..34b7b5652 --- /dev/null +++ b/src/pbkdf2.erl @@ -0,0 +1,157 @@ +% Licensed under the Apache License, Version 2.0 (the "License"); you may not +% use this file except in compliance with the License. You may obtain a copy of +% the License at +% +% http://www.apache.org/licenses/LICENSE-2.0 +% +% Unless required by applicable law or agreed to in writing, software +% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +% License for the specific language governing permissions and limitations under +% the License. + +-module(pbkdf2). + +-export([pbkdf2/4, pbkdf2/5, compare_secure/2, to_hex/1]). + +-type(hex_char() :: 48 .. 57 | 97 .. 102). +-type(hex_list() :: [hex_char()]). + +-type(digest_func_info() :: md4 | md5 | ripemd160 | sha | sha224 | sha256 | sha384 | sha512). + +-type(mac_func_info() :: {hmac, digest_func_info()} | digest_func_info()). + +-define(MAX_DERIVED_KEY_LENGTH, (1 bsl 32 - 1)). + +%%-------------------------------------------------------------------- +%% Public API +%%-------------------------------------------------------------------- + +-spec(pbkdf2(MacFunc, Password, Salt, Iterations) -> {ok, Key} | {error, derived_key_too_long} when + MacFunc :: mac_func_info(), + Password :: binary(), + Salt :: binary(), + Iterations :: integer(), + Key :: binary()). +pbkdf2(MacFunc, Password, Salt, Iterations) -> + MacFunc1 = resolve_mac_func(MacFunc), + DerivedLength = byte_size(MacFunc1(<<"test key">>, <<"test data">>)), + Bin = pbkdf2(MacFunc1, Password, Salt, Iterations, DerivedLength, 1, []), + {ok, Bin}. + +-spec(pbkdf2(MacFunc, Password, Salt, Iterations, DerivedLength) -> {ok, Key} | {error, derived_key_too_long} when + MacFunc :: mac_func_info(), + Password :: binary(), + Salt :: binary(), + Iterations :: integer(), + DerivedLength :: integer(), + Key :: binary()). +pbkdf2(_MacFunc, _Password, _Salt, _Iterations, DerivedLength) when DerivedLength > ?MAX_DERIVED_KEY_LENGTH -> + {error, derived_key_too_long}; +pbkdf2(MacFunc, Password, Salt, Iterations, DerivedLength) -> + MacFunc1 = resolve_mac_func(MacFunc), + Bin = pbkdf2(MacFunc1, Password, Salt, Iterations, DerivedLength, 1, []), + {ok, Bin}. + +-spec(to_hex(Data) -> HexData when + Data :: iolist(), + HexData :: binary() | hex_list()). +to_hex(<<>>) -> + <<>>; +to_hex(<>) -> + CharHex1 = to_hex_digit(Char div 16), + CharHex2 = to_hex_digit(Char rem 16), + RestHex = to_hex(Rest), + <>; +to_hex([]) -> + []; +to_hex([Char | Rest]) -> + [to_hex_digit(Char div 16), to_hex_digit(Char rem 16) | to_hex(Rest)]. + +%%-------------------------------------------------------------------- +%% Internal Functions +%%-------------------------------------------------------------------- + +-spec(pbkdf2(MacFunc, Password, Salt, Iterations, DerivedLength, BlockIndex, Acc) -> Key when + MacFunc :: fun((binary(), binary()) -> binary()), + Password :: binary(), + Salt :: binary(), + Iterations :: integer(), + DerivedLength :: integer(), + BlockIndex :: integer(), + Acc :: iolist(), + Key :: binary()). +pbkdf2(MacFunc, Password, Salt, Iterations, DerivedLength, BlockIndex, Acc) -> + case iolist_size(Acc) > DerivedLength of + true -> <> = iolist_to_binary(lists:reverse(Acc)), + Bin; + false -> Block = pbkdf2(MacFunc, Password, Salt, Iterations, BlockIndex, 1, <<>>, <<>>), + pbkdf2(MacFunc, Password, Salt, Iterations, DerivedLength, BlockIndex + 1, [Block | Acc]) + end. + +-spec(pbkdf2(MacFunc, Password, Salt, Iterations, BlockIndex, Iteration, Prev, Acc) -> Key when + MacFunc :: fun((binary(), binary()) -> binary()), + Password :: binary(), + Salt :: binary(), + Iterations :: integer(), + BlockIndex :: integer(), + Iteration :: integer(), + Prev :: binary(), + Acc :: binary(), + Key :: binary()). +pbkdf2(_MacFunc, _Password, _Salt, Iterations, _BlockIndex, Iteration, _Prev, Acc) when Iteration > Iterations -> + Acc; +pbkdf2(MacFunc, Password, Salt, Iterations, BlockIndex, 1, _Prev, _Acc) -> + InitialBlock = MacFunc(Password, <>), + pbkdf2(MacFunc, Password, Salt, Iterations, BlockIndex, 2, InitialBlock, InitialBlock); +pbkdf2(MacFunc, Password, Salt, Iterations, BlockIndex, Iteration, Prev, Acc) -> + Next = MacFunc(Password, Prev), + pbkdf2(MacFunc, Password, Salt, Iterations, BlockIndex, Iteration + 1, Next, crypto:exor(Next, Acc)). + +resolve_mac_func({hmac, DigestFunc}) -> + fun(Key, Data) -> + HMAC = crypto:hmac_init(DigestFunc, Key), + HMAC1 = crypto:hmac_update(HMAC, Data), + crypto:hmac_final(HMAC1) + end; + +resolve_mac_func(MacFunc) when is_function(MacFunc) -> + MacFunc; + +resolve_mac_func(md4) -> resolve_mac_func({hmac, md4}); +resolve_mac_func(md5) -> resolve_mac_func({hmac, md5}); +resolve_mac_func(ripemd160) -> resolve_mac_func({hmac, ripemd160}); +resolve_mac_func(sha) -> resolve_mac_func({hmac, sha}); +resolve_mac_func(sha224) -> resolve_mac_func({hmac, sha224}); +resolve_mac_func(sha256) -> resolve_mac_func({hmac, sha256}); +resolve_mac_func(sha384) -> resolve_mac_func({hmac, sha384}); +resolve_mac_func(sha512) -> resolve_mac_func({hmac, sha512}). + +%% Compare two strings or binaries for equality without short-circuits to avoid timing attacks. + +-spec(compare_secure(First, Second) -> boolean() when + First :: binary() | string(), + Second :: binary() | string()). +compare_secure(<>, <>) -> + compare_secure(binary_to_list(X), binary_to_list(Y)); +compare_secure(X, Y) when is_list(X) and is_list(Y) -> + case length(X) == length(Y) of + true -> compare_secure(X, Y, 0); + false -> false + end; +compare_secure(_X, _Y) -> false. + +-spec(compare_secure(First, Second, Accum) -> boolean() when + First :: string(), + Second :: string(), + Accum :: integer()). +compare_secure([X|RestX], [Y|RestY], Result) -> + compare_secure(RestX, RestY, (X bxor Y) bor Result); +compare_secure([], [], Result) -> + Result == 0. + +-spec(to_hex_digit(Nyble :: 0 .. 15) -> hex_char()). +to_hex_digit(N) when N < 10 -> + $0 + N; +to_hex_digit(N) -> + $a + N - 10.