feat(authentication): add a 16-byte salt in front of the password before hashing

This commit is contained in:
zhouzb 2021-06-09 16:53:40 +08:00
parent d838206a56
commit ae8be5d66f
5 changed files with 38 additions and 24 deletions

View File

@ -1,3 +1,3 @@
user_id,password_hash user_id,password_hash,salt
myuser3,8d41233e39c95b5da13361e354e1c9e639f07b27d397463a8f91b71ee07ccfb2 myuser3,13de23cc872cf8198797914e95b9ec4e123fd7aaea186aea824452ec0f651a91,DztjMHrbVWmzkF1/dKD/ag==
myuser4,5809df0154f3cb4ac5c3a5572eaca0c5f7f9d858e887fc675b2becab9feb19d1 myuser4,5231a927328f24e7254513819e47277feeb379a724f5e784ddbb09db42d322b7,+kGfV4AH+MR3f30zhoPIkQ==

1 user_id password_hash salt
2 myuser3 8d41233e39c95b5da13361e354e1c9e639f07b27d397463a8f91b71ee07ccfb2 13de23cc872cf8198797914e95b9ec4e123fd7aaea186aea824452ec0f651a91 DztjMHrbVWmzkF1/dKD/ag==
3 myuser4 5809df0154f3cb4ac5c3a5572eaca0c5f7f9d858e887fc675b2becab9feb19d1 5231a927328f24e7254513819e47277feeb379a724f5e784ddbb09db42d322b7 +kGfV4AH+MR3f30zhoPIkQ==

View File

@ -1,10 +1,12 @@
[ [
{ {
"user_id":"myuser1", "user_id":"myuser1",
"password_hash":"09343625c6c123d3434932fe1ce08bae5ac00a8f95bd746e10491b0bafdd1817" "password_hash":"3e4845e5fc818ac1bfe6a3f77ab665e7721700b5803b6f76def5dce6aacdc42c",
"salt": "LvLGNfMjUJhUpuWIubv4Gg=="
}, },
{ {
"user_id":"myuser2", "user_id":"myuser2",
"password_hash":"8767a7d316ad68cb607c7c805b859ffa78277dda13b7a3e2e8b53cad3cabbc6e" "password_hash":"54bd059dc88e6dd9158306a638215fdce5545eac351fdf2affc8ee94686711c5",
"salt": "wF1mavOutYiNvwyYw0PmbQ=="
} }
] ]

View File

@ -32,20 +32,19 @@
, list_users/1 , list_users/1
]). ]).
%% TODO: support bcrypt
-service_type(#{ -service_type(#{
name => mnesia, name => mnesia,
params_spec => #{ params_spec => #{
user_id_type => #{ user_id_type => #{
order => 1, order => 1,
type => string, type => string,
required => true,
enum => [<<"username">>, <<"clientid">>, <<"ip">>, <<"common name">>, <<"issuer">>], enum => [<<"username">>, <<"clientid">>, <<"ip">>, <<"common name">>, <<"issuer">>],
default => <<"username">> default => <<"username">>
}, },
password_hash_algorithm => #{ password_hash_algorithm => #{
order => 2, order => 2,
type => string, type => string,
required => true,
enum => [<<"plain">>, <<"md5">>, <<"sha">>, <<"sha256">>, <<"sha512">>], enum => [<<"plain">>, <<"md5">>, <<"sha">>, <<"sha256">>, <<"sha512">>],
default => <<"sha256">> default => <<"sha256">>
} }
@ -55,6 +54,7 @@
-record(user_info, -record(user_info,
{ user_id :: {user_group(), user_id()} { user_id :: {user_group(), user_id()}
, password_hash :: binary() , password_hash :: binary()
, salt :: binary()
}). }).
-type(user_group() :: {chain_id(), service_name()}). -type(user_group() :: {chain_id(), service_name()}).
@ -67,8 +67,6 @@
-define(TAB, mnesia_basic_auth). -define(TAB, mnesia_basic_auth).
%% TODO: Support salt
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Mnesia bootstrap %% Mnesia bootstrap
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
@ -103,8 +101,8 @@ authenticate(ClientInfo = #{password := Password},
case mnesia:dirty_read(?TAB, {UserGroup, UserID}) of case mnesia:dirty_read(?TAB, {UserGroup, UserID}) of
[] -> [] ->
ignore; ignore;
[#user_info{password_hash = Hash}] -> [#user_info{password_hash = Hash, salt = Salt}] ->
case Hash =:= emqx_passwd:hash(Algorithm, Password) of case Hash =:= emqx_passwd:hash(Algorithm, <<Salt/binary, Password/binary>>) of
true -> true ->
ok; ok;
false -> false ->
@ -220,9 +218,11 @@ import_users_from_csv(Filename, #{user_group := UserGroup}) ->
import(_UserGroup, []) -> import(_UserGroup, []) ->
ok; ok;
import(UserGroup, [#{<<"user_id">> := UserID, <<"password_hash">> := PasswordHash} | More]) import(UserGroup, [#{<<"user_id">> := UserID,
<<"password_hash">> := PasswordHash,
<<"salt">> := Salt} | More])
when is_binary(UserID) andalso is_binary(PasswordHash) -> when is_binary(UserID) andalso is_binary(PasswordHash) ->
import_user(UserGroup, UserID, PasswordHash), import_user(UserGroup, UserID, PasswordHash, Salt),
import(UserGroup, More); import(UserGroup, More);
import(_UserGroup, [_ | _More]) -> import(_UserGroup, [_ | _More]) ->
{error, bad_format}. {error, bad_format}.
@ -234,8 +234,9 @@ import(UserGroup, File, Seq) ->
Fields = binary:split(Line, [<<",">>, <<" ">>, <<"\n">>], [global, trim_all]), Fields = binary:split(Line, [<<",">>, <<" ">>, <<"\n">>], [global, trim_all]),
case get_user_info_by_seq(Fields, Seq) of case get_user_info_by_seq(Fields, Seq) of
{ok, #{user_id := UserID, {ok, #{user_id := UserID,
password_hash := PasswordHash}} -> password_hash := PasswordHash,
import_user(UserGroup, UserID, PasswordHash), salt := Salt}} ->
import_user(UserGroup, UserID, PasswordHash, Salt),
import(UserGroup, File, Seq); import(UserGroup, File, Seq);
{error, Reason} -> {error, Reason} ->
{error, Reason} {error, Reason}
@ -260,7 +261,7 @@ get_csv_header(File) ->
get_user_info_by_seq(Fields, Seq) -> get_user_info_by_seq(Fields, Seq) ->
get_user_info_by_seq(Fields, Seq, #{}). get_user_info_by_seq(Fields, Seq, #{}).
get_user_info_by_seq([], [], #{user_id := _, password_hash := _} = Acc) -> get_user_info_by_seq([], [], #{user_id := _, password_hash := _, salt := _} = Acc) ->
{ok, Acc}; {ok, Acc};
get_user_info_by_seq(_, [], _) -> get_user_info_by_seq(_, [], _) ->
{error, bad_format}; {error, bad_format};
@ -268,18 +269,27 @@ get_user_info_by_seq([UserID | More1], [<<"user_id">> | More2], Acc) ->
get_user_info_by_seq(More1, More2, Acc#{user_id => UserID}); get_user_info_by_seq(More1, More2, Acc#{user_id => UserID});
get_user_info_by_seq([PasswordHash | More1], [<<"password_hash">> | More2], Acc) -> get_user_info_by_seq([PasswordHash | More1], [<<"password_hash">> | More2], Acc) ->
get_user_info_by_seq(More1, More2, Acc#{password_hash => PasswordHash}); get_user_info_by_seq(More1, More2, Acc#{password_hash => PasswordHash});
get_user_info_by_seq([Salt | More1], [<<"salt">> | More2], Acc) ->
get_user_info_by_seq(More1, More2, Acc#{salt => Salt});
get_user_info_by_seq(_, _, _) -> get_user_info_by_seq(_, _, _) ->
{error, bad_format}. {error, bad_format}.
-compile({inline, [add/4]}). -compile({inline, [add/4]}).
add(UserGroup, UserID, Password, Algorithm) -> add(UserGroup, UserID, Password, Algorithm) ->
Salt = case Algorithm of
<<"plain">> -> <<>>;
_ -> crypto:strong_rand_bytes(16)
end,
SaltedPassword = <<Salt/binary, Password/binary>>,
Credential = #user_info{user_id = {UserGroup, UserID}, Credential = #user_info{user_id = {UserGroup, UserID},
password_hash = emqx_passwd:hash(Algorithm, Password)}, password_hash = emqx_passwd:hash(Algorithm, SaltedPassword),
salt = Salt},
mnesia:write(?TAB, Credential, write). mnesia:write(?TAB, Credential, write).
import_user(UserGroup, UserID, PasswordHash) -> import_user(UserGroup, UserID, PasswordHash, Salt) ->
Credential = #user_info{user_id = {UserGroup, UserID}, Credential = #user_info{user_id = {UserGroup, UserID},
password_hash = PasswordHash}, password_hash = PasswordHash,
salt = base64:decode(Salt)},
mnesia:write(?TAB, Credential, write). mnesia:write(?TAB, Credential, write).
delete_user2(UserInfo) -> delete_user2(UserInfo) ->

View File

@ -1,3 +1,3 @@
user_id,password_hash user_id,password_hash,salt
myuser3,8d41233e39c95b5da13361e354e1c9e639f07b27d397463a8f91b71ee07ccfb2 myuser3,13de23cc872cf8198797914e95b9ec4e123fd7aaea186aea824452ec0f651a91,DztjMHrbVWmzkF1/dKD/ag==
myuser4,5809df0154f3cb4ac5c3a5572eaca0c5f7f9d858e887fc675b2becab9feb19d1 myuser4,5231a927328f24e7254513819e47277feeb379a724f5e784ddbb09db42d322b7,+kGfV4AH+MR3f30zhoPIkQ==

1 user_id password_hash salt
2 myuser3 8d41233e39c95b5da13361e354e1c9e639f07b27d397463a8f91b71ee07ccfb2 13de23cc872cf8198797914e95b9ec4e123fd7aaea186aea824452ec0f651a91 DztjMHrbVWmzkF1/dKD/ag==
3 myuser4 5809df0154f3cb4ac5c3a5572eaca0c5f7f9d858e887fc675b2becab9feb19d1 5231a927328f24e7254513819e47277feeb379a724f5e784ddbb09db42d322b7 +kGfV4AH+MR3f30zhoPIkQ==

View File

@ -1,10 +1,12 @@
[ [
{ {
"user_id":"myuser1", "user_id":"myuser1",
"password_hash":"09343625c6c123d3434932fe1ce08bae5ac00a8f95bd746e10491b0bafdd1817" "password_hash":"3e4845e5fc818ac1bfe6a3f77ab665e7721700b5803b6f76def5dce6aacdc42c",
"salt": "LvLGNfMjUJhUpuWIubv4Gg=="
}, },
{ {
"user_id":"myuser2", "user_id":"myuser2",
"password_hash":"8767a7d316ad68cb607c7c805b859ffa78277dda13b7a3e2e8b53cad3cabbc6e" "password_hash":"54bd059dc88e6dd9158306a638215fdce5545eac351fdf2affc8ee94686711c5",
"salt": "wF1mavOutYiNvwyYw0PmbQ=="
} }
] ]