summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/rabbit.app.src6
-rw-r--r--src/rabbit.erl106
-rw-r--r--src/rabbit_control_main.erl16
-rw-r--r--src/rabbit_control_pbe.erl79
-rw-r--r--src/rabbit_mirror_queue_mode_nodes.erl36
-rw-r--r--src/rabbit_pbe.erl194
6 files changed, 420 insertions, 17 deletions
diff --git a/src/rabbit.app.src b/src/rabbit.app.src
index eeaf9b79c2..4638c20ff4 100644
--- a/src/rabbit.app.src
+++ b/src/rabbit.app.src
@@ -103,6 +103,12 @@
%% see rabbitmq-server#248
%% and rabbitmq-server#667
{channel_operation_timeout, 15000},
+ {decoder_config, [
+ {cipher, aes_cbc256},
+ {hash, sha512},
+ {iterations, 1000},
+ {passphrase, undefined}
+ ]},
%% rabbitmq-server-973
{lazy_queue_explicit_gc_run_operation_threshold, 250}
]}]}.
diff --git a/src/rabbit.erl b/src/rabbit.erl
index 5db2c40b66..089fc0dd1c 100644
--- a/src/rabbit.erl
+++ b/src/rabbit.erl
@@ -24,7 +24,7 @@
start_fhc/0]).
-export([start/2, stop/1, prep_stop/1]).
-export([start_apps/1, stop_apps/1]).
--export([log_locations/0, config_files/0]). %% for testing and mgmt-agent
+-export([log_locations/0, config_files/0, decrypt_config/2]). %% for testing and mgmt-agent
-ifdef(TEST).
@@ -465,6 +465,38 @@ stop_and_halt() ->
start_apps(Apps) ->
app_utils:load_applications(Apps),
+
+ DecoderConfig = case application:get_env(rabbit, decoder_config) of
+ undefined ->
+ [];
+ {ok, Val} ->
+ Val
+ end,
+ PassPhrase = case proplists:get_value(passphrase, DecoderConfig) of
+ prompt ->
+ IoDevice = get_input_iodevice(),
+ io:setopts(IoDevice, [{echo, false}]),
+ PP = lists:droplast(io:get_line(IoDevice,
+ "\nPlease enter the passphrase to unlock encrypted "
+ "configuration entries.\n\nPassphrase: ")),
+ io:setopts(IoDevice, [{echo, true}]),
+ io:format(IoDevice, "~n", []),
+ PP;
+ {file, Filename} ->
+ {ok, File} = file:read_file(Filename),
+ [PP|_] = binary:split(File, [<<"\r\n">>, <<"\n">>]),
+ PP;
+ PP ->
+ PP
+ end,
+ Algo = {
+ proplists:get_value(cipher, DecoderConfig, rabbit_pbe:default_cipher()),
+ proplists:get_value(hash, DecoderConfig, rabbit_pbe:default_hash()),
+ proplists:get_value(iterations, DecoderConfig, rabbit_pbe:default_iterations()),
+ PassPhrase
+ },
+ decrypt_config(Apps, Algo),
+
OrderedApps = app_utils:app_dependency_order(Apps, false),
case lists:member(rabbit, Apps) of
false -> rabbit_boot_steps:run_boot_steps(Apps); %% plugin activation
@@ -473,6 +505,78 @@ start_apps(Apps) ->
ok = app_utils:start_applications(OrderedApps,
handle_app_error(could_not_start)).
+%% This function retrieves the correct IoDevice for requesting
+%% input. The problem with using the default IoDevice is that
+%% the Erlang shell prevents us from getting the input.
+%%
+%% Instead we therefore look for the io process used by the
+%% shell and if it can't be found (because the shell is not
+%% started e.g with -noshell) we use the 'user' process.
+%%
+%% This function will not work when either -oldshell or -noinput
+%% options are passed to erl.
+get_input_iodevice() ->
+ case whereis(user) of
+ undefined -> user;
+ User ->
+ case group:interfaces(User) of
+ [] ->
+ user;
+ [{user_drv, Drv}] ->
+ case user_drv:interfaces(Drv) of
+ [] ->
+ user;
+ [{current_group, IoDevice}] ->
+ IoDevice
+ end
+ end
+ end.
+
+decrypt_config([], _) ->
+ ok;
+decrypt_config([App|Apps], Algo) ->
+ decrypt_app(App, application:get_all_env(App), Algo),
+ decrypt_config(Apps, Algo).
+
+decrypt_app(_, [], _) ->
+ ok;
+decrypt_app(App, [{Key, Value}|Tail], Algo) ->
+ try begin
+ case decrypt(Value, Algo) of
+ Value ->
+ ok;
+ NewValue ->
+ application:set_env(App, Key, NewValue)
+ end
+ end
+ catch
+ exit:{bad_configuration, decoder_config} ->
+ exit({bad_configuration, decoder_config});
+ _:Msg ->
+ rabbit_log:info("Error while decrypting key '~p'. Please check encrypted value, passphrase, and encryption configuration~n", [Key]),
+ exit({decryption_error, {key, Key}, Msg})
+ end,
+ decrypt_app(App, Tail, Algo).
+
+decrypt({encrypted, _}, {_, _, _, undefined}) ->
+ exit({bad_configuration, decoder_config});
+decrypt({encrypted, EncValue}, {Cipher, Hash, Iterations, Password}) ->
+ rabbit_pbe:decrypt_term(Cipher, Hash, Iterations, Password, EncValue);
+decrypt(List, Algo) when is_list(List) ->
+ decrypt_list(List, Algo, []);
+decrypt(Value, _) ->
+ Value.
+
+%% We make no distinction between strings and other lists.
+%% When we receive a string, we loop through each element
+%% and ultimately return the string unmodified, as intended.
+decrypt_list([], _, Acc) ->
+ lists:reverse(Acc);
+decrypt_list([{Key, Value}|Tail], Algo, Acc) when Key =/= encrypted ->
+ decrypt_list(Tail, Algo, [{Key, decrypt(Value, Algo)}|Acc]);
+decrypt_list([Value|Tail], Algo, Acc) ->
+ decrypt_list(Tail, Algo, [decrypt(Value, Algo)|Acc]).
+
stop_apps(Apps) ->
ok = app_utils:stop_applications(
Apps, handle_app_error(error_during_shutdown)),
diff --git a/src/rabbit_control_main.erl b/src/rabbit_control_main.erl
index f48f0349aa..1619f25494 100644
--- a/src/rabbit_control_main.erl
+++ b/src/rabbit_control_main.erl
@@ -97,7 +97,8 @@
{trace_off, [?VHOST_DEF]},
set_vm_memory_high_watermark,
set_disk_free_limit,
- help
+ help,
+ {encode, [?DECODE_DEF, ?CIPHER_DEF, ?HASH_DEF, ?ITERATIONS_DEF, ?LIST_CIPHERS_DEF, ?LIST_HASHES_DEF]}
]).
-define(GLOBAL_QUERIES,
@@ -119,7 +120,7 @@
[stop, stop_app, start_app, wait, reset, force_reset, rotate_logs,
join_cluster, change_cluster_node_type, update_cluster_nodes,
forget_cluster_node, rename_cluster_node, cluster_status, status,
- environment, eval, force_boot, help, hipe_compile]).
+ environment, eval, force_boot, help, hipe_compile, encode]).
%% [Command | {Command, DefaultTimeoutInMilliSeconds}]
-define(COMMANDS_WITH_TIMEOUT,
@@ -613,6 +614,17 @@ action(eval, Node, [Expr], _Opts, _Inform) ->
action(help, _Node, _Args, _Opts, _Inform) ->
io:format("~s", [rabbit_ctl_usage:usage()]);
+action(encode, _Node, Args, Opts, _Inform) ->
+ ListCiphers = lists:member({?LIST_CIPHERS_OPT, true}, Opts),
+ ListHashes = lists:member({?LIST_HASHES_OPT, true}, Opts),
+ Decode = lists:member({?DECODE_OPT, true}, Opts),
+ Cipher = list_to_atom(proplists:get_value(?CIPHER_OPT, Opts)),
+ Hash = list_to_atom(proplists:get_value(?HASH_OPT, Opts)),
+ Iterations = list_to_integer(proplists:get_value(?ITERATIONS_OPT, Opts)),
+
+ {_, Msg} = rabbit_control_pbe:encode(ListCiphers, ListHashes, Decode, Cipher, Hash, Iterations, Args),
+ io:format(Msg ++ "~n");
+
action(Command, Node, Args, Opts, Inform) ->
%% For backward compatibility, run commands accepting a timeout with
%% the default timeout.
diff --git a/src/rabbit_control_pbe.erl b/src/rabbit_control_pbe.erl
new file mode 100644
index 0000000000..2fa2c90a6e
--- /dev/null
+++ b/src/rabbit_control_pbe.erl
@@ -0,0 +1,79 @@
+% The contents of this file are subject to the Mozilla Public License
+%% Version 1.1 (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.mozilla.org/MPL/
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and
+%% limitations under the License.
+%%
+%% The Original Code is RabbitMQ.
+%%
+%% The Initial Developer of the Original Code is GoPivotal, Inc.
+%% Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved.
+%%
+
+-module(rabbit_control_pbe).
+
+-export([encode/7]).
+
+% for testing purposes
+-export([evaluate_input_as_term/1]).
+
+encode(ListCiphers, _ListHashes, _Decode, _Cipher, _Hash, _Iterations, _Args) when ListCiphers ->
+ {ok, io_lib:format("~p", [rabbit_pbe:supported_ciphers()])};
+
+encode(_ListCiphers, ListHashes, _Decode, _Cipher, _Hash, _Iterations, _Args) when ListHashes ->
+ {ok, io_lib:format("~p", [rabbit_pbe:supported_hashes()])};
+
+encode(_ListCiphers, _ListHashes, Decode, Cipher, Hash, Iterations, Args) ->
+ CipherExists = lists:member(Cipher, rabbit_pbe:supported_ciphers()),
+ HashExists = lists:member(Hash, rabbit_pbe:supported_hashes()),
+ encode_encrypt_decrypt(CipherExists, HashExists, Decode, Cipher, Hash, Iterations, Args).
+
+encode_encrypt_decrypt(CipherExists, _HashExists, _Decode, _Cipher, _Hash, _Iterations, _Args) when CipherExists =:= false ->
+ {error, io_lib:format("The requested cipher is not supported", [])};
+
+encode_encrypt_decrypt(_CipherExists, HashExists, _Decode, _Cipher, _Hash, _Iterations, _Args) when HashExists =:= false ->
+ {error, io_lib:format("The requested hash is not supported", [])};
+
+encode_encrypt_decrypt(_CipherExists, _HashExists, _Decode, _Cipher, _Hash, Iterations, _Args) when Iterations =< 0 ->
+ {error, io_lib:format("The requested number of iterations is incorrect", [])};
+
+encode_encrypt_decrypt(_CipherExists, _HashExists, Decode, Cipher, Hash, Iterations, Args) when length(Args) == 2, Decode =:= false ->
+ [Value, PassPhrase] = Args,
+ try begin
+ TermValue = evaluate_input_as_term(Value),
+ Result = rabbit_pbe:encrypt_term(Cipher, Hash, Iterations, list_to_binary(PassPhrase), TermValue),
+ {ok, io_lib:format("~p", [{encrypted, Result}])}
+ end
+ catch
+ _:Msg -> {error, io_lib:format("Error during cipher operation: ~p", [Msg])}
+ end;
+
+encode_encrypt_decrypt(_CipherExists, _HashExists, Decode, Cipher, Hash, Iterations, Args) when length(Args) == 2, Decode ->
+ [Value, PassPhrase] = Args,
+ try begin
+ TermValue = evaluate_input_as_term(Value),
+ TermToDecrypt = case TermValue of
+ {encrypted, EncryptedTerm} ->
+ EncryptedTerm;
+ _ ->
+ TermValue
+ end,
+ Result = rabbit_pbe:decrypt_term(Cipher, Hash, Iterations, list_to_binary(PassPhrase), TermToDecrypt),
+ {ok, io_lib:format("~p", [Result])}
+ end
+ catch
+ _:Msg -> {error, io_lib:format("Error during cipher operation: ~p", [Msg])}
+ end;
+
+encode_encrypt_decrypt(_CipherExists, _HashExists, _Decode, _Cipher, _Hash, _Iterations, _Args) ->
+ {error, io_lib:format("Please provide a value to encode/decode and a passphrase", [])}.
+
+evaluate_input_as_term(Input) ->
+ {ok,Tokens,_EndLine} = erl_scan:string(Input ++ "."),
+ {ok,AbsForm} = erl_parse:parse_exprs(Tokens),
+ {value,TermValue,_Bs} = erl_eval:exprs(AbsForm, erl_eval:new_bindings()),
+ TermValue.
diff --git a/src/rabbit_mirror_queue_mode_nodes.erl b/src/rabbit_mirror_queue_mode_nodes.erl
index e63f340373..31c55722a5 100644
--- a/src/rabbit_mirror_queue_mode_nodes.erl
+++ b/src/rabbit_mirror_queue_mode_nodes.erl
@@ -32,29 +32,37 @@
description() ->
[{description, <<"Mirror queue to specified nodes">>}].
-suggested_queue_nodes(Nodes0, MNode, _SNodes, SSNodes, Poss) ->
- Nodes1 = [list_to_atom(binary_to_list(Node)) || Node <- Nodes0],
+suggested_queue_nodes(PolicyNodes0, CurrentMaster, _SNodes, SSNodes, NodesRunningRabbitMQ) ->
+ PolicyNodes1 = [list_to_atom(binary_to_list(Node)) || Node <- PolicyNodes0],
%% If the current master is not in the nodes specified, then what we want
%% to do depends on whether there are any synchronised slaves. If there
%% are then we can just kill the current master - the admin has asked for
%% a migration and we should give it to them. If there are not however
%% then we must keep the master around so as not to lose messages.
- Nodes = case SSNodes of
- [] -> lists:usort([MNode | Nodes1]);
- _ -> Nodes1
- end,
- Unavailable = Nodes -- Poss,
- Available = Nodes -- Unavailable,
- case Available of
+
+ PolicyNodes = case SSNodes of
+ [] -> lists:usort([CurrentMaster | PolicyNodes1]);
+ _ -> PolicyNodes1
+ end,
+ Unavailable = PolicyNodes -- NodesRunningRabbitMQ,
+ AvailablePolicyNodes = PolicyNodes -- Unavailable,
+ case AvailablePolicyNodes of
[] -> %% We have never heard of anything? Not much we can do but
%% keep the master alive.
- {MNode, []};
- _ -> case lists:member(MNode, Available) of
- true -> {MNode, Available -- [MNode]};
+ {CurrentMaster, []};
+ _ -> case lists:member(CurrentMaster, AvailablePolicyNodes) of
+ true -> {CurrentMaster,
+ AvailablePolicyNodes -- [CurrentMaster]};
false -> %% Make sure the new master is synced! In order to
%% get here SSNodes must not be empty.
- [NewMNode | _] = SSNodes,
- {NewMNode, Available -- [NewMNode]}
+ SyncPolicyNodes = [Node ||
+ Node <- AvailablePolicyNodes,
+ lists:member(Node, SSNodes)],
+ NewMaster = case SyncPolicyNodes of
+ [Node | _] -> Node;
+ [] -> erlang:hd(SSNodes)
+ end,
+ {NewMaster, AvailablePolicyNodes -- [NewMaster]}
end
end.
diff --git a/src/rabbit_pbe.erl b/src/rabbit_pbe.erl
new file mode 100644
index 0000000000..f4998d4a13
--- /dev/null
+++ b/src/rabbit_pbe.erl
@@ -0,0 +1,194 @@
+%% The contents of this file are subject to the Mozilla Public License
+%% Version 1.1 (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.mozilla.org/MPL/
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and
+%% limitations under the License.
+%%
+%% The Original Code is RabbitMQ.
+%%
+%% The Initial Developer of the Original Code is GoPivotal, Inc.
+%% Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved.
+%%
+
+-module(rabbit_pbe).
+
+-export([supported_ciphers/0, supported_hashes/0, default_cipher/0, default_hash/0, default_iterations/0]).
+-export([encrypt_term/5, decrypt_term/5]).
+-export([encrypt/5, decrypt/5]).
+
+%% Supported ciphers and hashes
+
+supported_ciphers() ->
+ proplists:get_value(ciphers, crypto:supports())
+ -- [aes_ctr, aes_ecb, des_ecb, blowfish_ecb, rc4, aes_gcm].
+
+supported_hashes() ->
+ proplists:get_value(hashs, crypto:supports())
+ -- [md4, ripemd160].
+
+%% Default encryption parameters (keep those in sync with rabbit.app.src)
+default_cipher() ->
+ aes_cbc256.
+
+default_hash() ->
+ sha512.
+
+default_iterations() ->
+ 1000.
+
+%% Encryption/decryption of arbitrary Erlang terms.
+
+encrypt_term(Cipher, Hash, Iterations, PassPhrase, Term) ->
+ encrypt(Cipher, Hash, Iterations, PassPhrase, term_to_binary(Term)).
+
+decrypt_term(Cipher, Hash, Iterations, PassPhrase, Base64Binary) ->
+ binary_to_term(decrypt(Cipher, Hash, Iterations, PassPhrase, Base64Binary)).
+
+%% The cipher for encryption is from the list of supported ciphers.
+%% The hash for generating the key from the passphrase is from the list
+%% of supported hashes. See crypto:supports/0 to obtain both lists.
+%% The key is generated by applying the hash N times with N >= 1.
+%%
+%% The encrypt/5 function returns a base64 binary and the decrypt/5
+%% function accepts that same base64 binary.
+
+-spec encrypt(crypto:block_cipher(), crypto:hash_algorithms(),
+ pos_integer(), iodata(), binary()) -> binary().
+encrypt(Cipher, Hash, Iterations, PassPhrase, ClearText) ->
+ Salt = crypto:strong_rand_bytes(16),
+ Ivec = crypto:strong_rand_bytes(iv_length(Cipher)),
+ Key = make_key(Cipher, Hash, Iterations, PassPhrase, Salt),
+ Binary = crypto:block_encrypt(Cipher, Key, Ivec, pad(Cipher, ClearText)),
+ base64:encode(<< Salt/binary, Ivec/binary, Binary/binary >>).
+
+-spec decrypt(crypto:block_cipher(), crypto:hash_algorithms(),
+ pos_integer(), iodata(), binary()) -> binary().
+decrypt(Cipher, Hash, Iterations, PassPhrase, Base64Binary) ->
+ IvLength = iv_length(Cipher),
+ << Salt:16/binary, Ivec:IvLength/binary, Binary/bits >> = base64:decode(Base64Binary),
+ Key = make_key(Cipher, Hash, Iterations, PassPhrase, Salt),
+ unpad(crypto:block_decrypt(Cipher, Key, Ivec, Binary)).
+
+%% Generate a key from a passphrase.
+
+make_key(Cipher, Hash, Iterations, PassPhrase, Salt) ->
+ Key = pbdkdf2(PassPhrase, Salt, Iterations, key_length(Cipher),
+ fun crypto:hmac/4, Hash, hash_length(Hash)),
+ if
+ Cipher =:= des3_cbc; Cipher =:= des3_cbf; Cipher =:= des3_cfb; Cipher =:= des_ede3 ->
+ << A:8/binary, B:8/binary, C:8/binary >> = Key,
+ [A, B, C];
+ true ->
+ Key
+ end.
+
+%% Functions to pad/unpad input to a multiplier of block size.
+
+pad(Cipher, Data) ->
+ BlockSize = block_size(Cipher),
+ N = BlockSize - (byte_size(Data) rem BlockSize),
+ Pad = list_to_binary(lists:duplicate(N, N)),
+ <<Data/binary, Pad/binary>>.
+
+unpad(Data) ->
+ N = binary:last(Data),
+ binary:part(Data, 0, byte_size(Data) - N).
+
+%% These functions are necessary because the current Erlang crypto interface
+%% is lacking interfaces to the following OpenSSL functions:
+%%
+%% * int EVP_MD_size(const EVP_MD *md);
+%% * int EVP_CIPHER_iv_length(const EVP_CIPHER *e);
+%% * int EVP_CIPHER_key_length(const EVP_CIPHER *e);
+%% * int EVP_CIPHER_block_size(const EVP_CIPHER *e);
+
+hash_length(md4) -> 16;
+hash_length(md5) -> 16;
+hash_length(sha) -> 20;
+hash_length(sha224) -> 28;
+hash_length(sha256) -> 32;
+hash_length(sha384) -> 48;
+hash_length(sha512) -> 64.
+
+iv_length(des_cbc) -> 8;
+iv_length(des_cfb) -> 8;
+iv_length(des3_cbc) -> 8;
+iv_length(des3_cbf) -> 8;
+iv_length(des3_cfb) -> 8;
+iv_length(des_ede3) -> 8;
+iv_length(blowfish_cbc) -> 8;
+iv_length(blowfish_cfb64) -> 8;
+iv_length(blowfish_ofb64) -> 8;
+iv_length(rc2_cbc) -> 8;
+iv_length(aes_cbc) -> 16;
+iv_length(aes_cbc128) -> 16;
+iv_length(aes_cfb8) -> 16;
+iv_length(aes_cfb128) -> 16;
+iv_length(aes_cbc256) -> 16;
+iv_length(aes_ige256) -> 32.
+
+key_length(des_cbc) -> 8;
+key_length(des_cfb) -> 8;
+key_length(des3_cbc) -> 24;
+key_length(des3_cbf) -> 24;
+key_length(des3_cfb) -> 24;
+key_length(des_ede3) -> 24;
+key_length(blowfish_cbc) -> 16;
+key_length(blowfish_cfb64) -> 16;
+key_length(blowfish_ofb64) -> 16;
+key_length(rc2_cbc) -> 16;
+key_length(aes_cbc) -> 16;
+key_length(aes_cbc128) -> 16;
+key_length(aes_cfb8) -> 16;
+key_length(aes_cfb128) -> 16;
+key_length(aes_cbc256) -> 32;
+key_length(aes_ige256) -> 16.
+
+block_size(aes_cbc256) -> 32;
+block_size(aes_cbc128) -> 32;
+block_size(aes_ige256) -> 32;
+block_size(aes_cbc) -> 32;
+block_size(_) -> 8.
+
+%% The following was taken from OTP's lib/public_key/src/pubkey_pbe.erl
+%%
+%% This is an undocumented interface to password-based encryption algorithms.
+%% These functions have been copied here to stay compatible with R16B03.
+
+%%--------------------------------------------------------------------
+-spec pbdkdf2(string(), iodata(), integer(), integer(), fun(), atom(), integer())
+ -> binary().
+%%
+%% Description: Implements password based decryption key derive function 2.
+%% Exported mainly for testing purposes.
+%%--------------------------------------------------------------------
+pbdkdf2(Password, Salt, Count, DerivedKeyLen, Prf, PrfHash, PrfOutputLen)->
+ NumBlocks = ceiling(DerivedKeyLen / PrfOutputLen),
+ NumLastBlockOctets = DerivedKeyLen - (NumBlocks - 1) * PrfOutputLen ,
+ blocks(NumBlocks, NumLastBlockOctets, 1, Password, Salt,
+ Count, Prf, PrfHash, PrfOutputLen, <<>>).
+
+blocks(1, N, Index, Password, Salt, Count, Prf, PrfHash, PrfLen, Acc) ->
+ <<XorSum:N/binary, _/binary>> = xor_sum(Password, Salt, Count, Index, Prf, PrfHash, PrfLen),
+ <<Acc/binary, XorSum/binary>>;
+blocks(NumBlocks, N, Index, Password, Salt, Count, Prf, PrfHash, PrfLen, Acc) ->
+ XorSum = xor_sum(Password, Salt, Count, Index, Prf, PrfHash, PrfLen),
+ blocks(NumBlocks -1, N, Index +1, Password, Salt, Count, Prf, PrfHash,
+ PrfLen, <<Acc/binary, XorSum/binary>>).
+
+xor_sum(Password, Salt, Count, Index, Prf, PrfHash, PrfLen) ->
+ Result = Prf(PrfHash, Password, [Salt,<<Index:32/unsigned-big-integer>>], PrfLen),
+ do_xor_sum(Prf, PrfHash, PrfLen, Result, Password, Count-1, Result).
+
+do_xor_sum(_, _, _, _, _, 0, Acc) ->
+ Acc;
+do_xor_sum(Prf, PrfHash, PrfLen, Prev, Password, Count, Acc) ->
+ Result = Prf(PrfHash, Password, Prev, PrfLen),
+ do_xor_sum(Prf, PrfHash, PrfLen, Result, Password, Count-1, crypto:exor(Acc, Result)).
+
+ceiling(Float) ->
+ erlang:round(Float + 0.5).