feat(auth): support bcrypt

This commit is contained in:
zhouzb 2021-06-09 18:57:08 +08:00
parent ae8be5d66f
commit bb62b44554
5 changed files with 71 additions and 48 deletions

View File

@ -1,3 +1,3 @@
user_id,password_hash,salt user_id,password_hash,salt
myuser3,13de23cc872cf8198797914e95b9ec4e123fd7aaea186aea824452ec0f651a91,DztjMHrbVWmzkF1/dKD/ag== myuser3,b6c743545a7817ae8c8f624371d5f5f0373234bb0ff36b8ffbf19bce0e06ab75,de1024f462fb83910fd13151bd4bd235
myuser4,5231a927328f24e7254513819e47277feeb379a724f5e784ddbb09db42d322b7,+kGfV4AH+MR3f30zhoPIkQ== myuser4,ee68c985a69208b6eda8c6c9b4c7c2d2b15ee2352cdd64a903171710a99182e8,ad773b5be9dd0613fe6c2f4d8c403139

1 user_id password_hash salt
2 myuser3 13de23cc872cf8198797914e95b9ec4e123fd7aaea186aea824452ec0f651a91 b6c743545a7817ae8c8f624371d5f5f0373234bb0ff36b8ffbf19bce0e06ab75 DztjMHrbVWmzkF1/dKD/ag== de1024f462fb83910fd13151bd4bd235
3 myuser4 5231a927328f24e7254513819e47277feeb379a724f5e784ddbb09db42d322b7 ee68c985a69208b6eda8c6c9b4c7c2d2b15ee2352cdd64a903171710a99182e8 +kGfV4AH+MR3f30zhoPIkQ== ad773b5be9dd0613fe6c2f4d8c403139

View File

@ -1,12 +1,12 @@
[ [
{ {
"user_id":"myuser1", "user_id":"myuser1",
"password_hash":"3e4845e5fc818ac1bfe6a3f77ab665e7721700b5803b6f76def5dce6aacdc42c", "password_hash":"c5e46903df45e5dc096dc74657610dbee8deaacae656df88a1788f1847390242",
"salt": "LvLGNfMjUJhUpuWIubv4Gg==" "salt": "e378187547bf2d6f0545a3f441aa4d8a"
}, },
{ {
"user_id":"myuser2", "user_id":"myuser2",
"password_hash":"54bd059dc88e6dd9158306a638215fdce5545eac351fdf2affc8ee94686711c5", "password_hash":"f4d17f300b11e522fd33f497c11b126ef1ea5149c74d2220f9a16dc876d4567b",
"salt": "wF1mavOutYiNvwyYw0PmbQ==" "salt": "6d3f9bd5b54d94b98adbcfe10b6d181f"
} }
] ]

View File

@ -45,8 +45,13 @@
password_hash_algorithm => #{ password_hash_algorithm => #{
order => 2, order => 2,
type => string, type => string,
enum => [<<"plain">>, <<"md5">>, <<"sha">>, <<"sha256">>, <<"sha512">>], enum => [<<"plain">>, <<"md5">>, <<"sha">>, <<"sha256">>, <<"sha512">>, <<"bcrypt">>],
default => <<"sha256">> default => <<"sha256">>
},
salt_rounds => #{
order => 3,
type => number,
default => 10
} }
} }
}). }).
@ -84,10 +89,13 @@ mnesia(copy) ->
ok = ekka_mnesia:copy_table(?TAB, disc_copies). ok = ekka_mnesia:copy_table(?TAB, disc_copies).
create(ChainID, ServiceName, #{<<"user_id_type">> := Type, create(ChainID, ServiceName, #{<<"user_id_type">> := Type,
<<"password_hash_algorithm">> := Algorithm}) -> <<"password_hash_algorithm">> := Algorithm,
<<"salt_rounds">> := SaltRounds}) ->
Algorithm =:= <<"bcrypt">> andalso ({ok, _} = application:ensure_all_started(bcrypt)),
State = #{user_group => {ChainID, ServiceName}, State = #{user_group => {ChainID, ServiceName},
user_id_type => binary_to_atom(Type, utf8), user_id_type => binary_to_atom(Type, utf8),
password_hash_algorithm => binary_to_atom(Algorithm, utf8)}, password_hash_algorithm => binary_to_atom(Algorithm, utf8),
salt_rounds => SaltRounds},
{ok, State}. {ok, State}.
update(ChainID, ServiceName, Params, _State) -> update(ChainID, ServiceName, Params, _State) ->
@ -101,12 +109,10 @@ 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, salt = Salt}] -> [#user_info{password_hash = PasswordHash, salt = Salt}] ->
case Hash =:= emqx_passwd:hash(Algorithm, <<Salt/binary, Password/binary>>) of case PasswordHash =:= hash(Algorithm, Password, Salt) of
true -> true -> ok;
ok; false -> {stop, bad_password}
false ->
{stop, bad_password}
end end
end. end.
@ -132,13 +138,12 @@ import_users(Filename0, State) ->
add_user(#{<<"user_id">> := UserID, add_user(#{<<"user_id">> := UserID,
<<"password">> := Password}, <<"password">> := Password},
#{user_group := UserGroup, #{user_group := UserGroup} = State) ->
password_hash_algorithm := Algorithm}) ->
trans( trans(
fun() -> fun() ->
case mnesia:read(?TAB, {UserGroup, UserID}, write) of case mnesia:read(?TAB, {UserGroup, UserID}, write) of
[] -> [] ->
add(UserGroup, UserID, Password, Algorithm), add(UserID, Password, State),
{ok, #{user_id => UserID}}; {ok, #{user_id => UserID}};
[_] -> [_] ->
{error, already_exist} {error, already_exist}
@ -157,15 +162,14 @@ delete_user(UserID, #{user_group := UserGroup}) ->
end). end).
update_user(UserID, #{<<"password">> := Password}, update_user(UserID, #{<<"password">> := Password},
#{user_group := UserGroup, #{user_group := UserGroup} = State) ->
password_hash_algorithm := Algorithm}) ->
trans( trans(
fun() -> fun() ->
case mnesia:read(?TAB, {UserGroup, UserID}, write) of case mnesia:read(?TAB, {UserGroup, UserID}, write) of
[] -> [] ->
{error, not_found}; {error, not_found};
[_] -> [_] ->
add(UserGroup, UserID, Password, Algorithm), add(UserID, Password, State),
{ok, #{user_id => UserID}} {ok, #{user_id => UserID}}
end end
end). end).
@ -219,10 +223,10 @@ import_users_from_csv(Filename, #{user_group := UserGroup}) ->
import(_UserGroup, []) -> import(_UserGroup, []) ->
ok; ok;
import(UserGroup, [#{<<"user_id">> := UserID, import(UserGroup, [#{<<"user_id">> := UserID,
<<"password_hash">> := PasswordHash, <<"password_hash">> := PasswordHash} = UserInfo | More])
<<"salt">> := Salt} | More])
when is_binary(UserID) andalso is_binary(PasswordHash) -> when is_binary(UserID) andalso is_binary(PasswordHash) ->
import_user(UserGroup, UserID, PasswordHash, Salt), Salt = maps:get(<<"salt">>, UserInfo, <<>>),
insert_user(UserGroup, UserID, PasswordHash, Salt),
import(UserGroup, More); import(UserGroup, More);
import(_UserGroup, [_ | _More]) -> import(_UserGroup, [_ | _More]) ->
{error, bad_format}. {error, bad_format}.
@ -234,9 +238,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} = UserInfo} ->
salt := Salt}} -> Salt = maps:get(salt, UserInfo, <<>>),
import_user(UserGroup, UserID, PasswordHash, Salt), insert_user(UserGroup, UserID, PasswordHash, Salt),
import(UserGroup, File, Seq); import(UserGroup, File, Seq);
{error, Reason} -> {error, Reason} ->
{error, Reason} {error, Reason}
@ -263,6 +267,8 @@ get_user_info_by_seq(Fields, Seq) ->
get_user_info_by_seq([], [], #{user_id := _, password_hash := _, salt := _} = Acc) -> get_user_info_by_seq([], [], #{user_id := _, password_hash := _, salt := _} = Acc) ->
{ok, Acc}; {ok, Acc};
get_user_info_by_seq([], [], #{user_id := _, password_hash := _} = Acc) ->
{ok, Acc};
get_user_info_by_seq(_, [], _) -> get_user_info_by_seq(_, [], _) ->
{error, bad_format}; {error, bad_format};
get_user_info_by_seq([UserID | More1], [<<"user_id">> | More2], Acc) -> get_user_info_by_seq([UserID | More1], [<<"user_id">> | More2], Acc) ->
@ -274,22 +280,39 @@ get_user_info_by_seq([Salt | More1], [<<"salt">> | More2], Acc) ->
get_user_info_by_seq(_, _, _) -> get_user_info_by_seq(_, _, _) ->
{error, bad_format}. {error, bad_format}.
-compile({inline, [add/4]}). -compile({inline, [add/3]}).
add(UserGroup, UserID, Password, Algorithm) -> add(UserID, Password, #{user_group := UserGroup,
Salt = case Algorithm of password_hash_algorithm := Algorithm} = State) ->
<<"plain">> -> <<>>; Salt = gen_salt(State),
_ -> crypto:strong_rand_bytes(16) PasswordHash = hash(Algorithm, Password, Salt),
end, case Algorithm of
SaltedPassword = <<Salt/binary, Password/binary>>, bcrypt -> insert_user(UserGroup, UserID, PasswordHash);
Credential = #user_info{user_id = {UserGroup, UserID}, _ -> insert_user(UserGroup, UserID, PasswordHash, Salt)
password_hash = emqx_passwd:hash(Algorithm, SaltedPassword), end.
salt = Salt},
mnesia:write(?TAB, Credential, write).
import_user(UserGroup, UserID, PasswordHash, Salt) -> gen_salt(#{password_hash_algorithm := plain}) ->
Credential = #user_info{user_id = {UserGroup, UserID}, <<>>;
password_hash = PasswordHash, gen_salt(#{password_hash_algorithm := bcrypt,
salt = base64:decode(Salt)}, salt_rounds := Rounds}) ->
{ok, Salt} = bcrypt:gen_salt(Rounds),
Salt;
gen_salt(_) ->
<<X:128/big-unsigned-integer>> = crypto:strong_rand_bytes(16),
iolist_to_binary(io_lib:format("~32.16.0b", [X])).
hash(bcrypt, Password, Salt) ->
{ok, Hash} = bcrypt:hashpw(Password, Salt),
list_to_binary(Hash);
hash(Algorithm, Password, Salt) ->
emqx_passwd:hash(Algorithm, <<Salt/binary, Password/binary>>).
insert_user(UserGroup, UserID, PasswordHash) ->
insert_user(UserGroup, UserID, PasswordHash, <<>>).
insert_user(UserGroup, UserID, PasswordHash, Salt) ->
Credential = #user_info{user_id = {UserGroup, UserID},
password_hash = PasswordHash,
salt = 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,salt user_id,password_hash,salt
myuser3,13de23cc872cf8198797914e95b9ec4e123fd7aaea186aea824452ec0f651a91,DztjMHrbVWmzkF1/dKD/ag== myuser3,b6c743545a7817ae8c8f624371d5f5f0373234bb0ff36b8ffbf19bce0e06ab75,de1024f462fb83910fd13151bd4bd235
myuser4,5231a927328f24e7254513819e47277feeb379a724f5e784ddbb09db42d322b7,+kGfV4AH+MR3f30zhoPIkQ== myuser4,ee68c985a69208b6eda8c6c9b4c7c2d2b15ee2352cdd64a903171710a99182e8,ad773b5be9dd0613fe6c2f4d8c403139

1 user_id password_hash salt
2 myuser3 13de23cc872cf8198797914e95b9ec4e123fd7aaea186aea824452ec0f651a91 b6c743545a7817ae8c8f624371d5f5f0373234bb0ff36b8ffbf19bce0e06ab75 DztjMHrbVWmzkF1/dKD/ag== de1024f462fb83910fd13151bd4bd235
3 myuser4 5231a927328f24e7254513819e47277feeb379a724f5e784ddbb09db42d322b7 ee68c985a69208b6eda8c6c9b4c7c2d2b15ee2352cdd64a903171710a99182e8 +kGfV4AH+MR3f30zhoPIkQ== ad773b5be9dd0613fe6c2f4d8c403139

View File

@ -1,12 +1,12 @@
[ [
{ {
"user_id":"myuser1", "user_id":"myuser1",
"password_hash":"3e4845e5fc818ac1bfe6a3f77ab665e7721700b5803b6f76def5dce6aacdc42c", "password_hash":"c5e46903df45e5dc096dc74657610dbee8deaacae656df88a1788f1847390242",
"salt": "LvLGNfMjUJhUpuWIubv4Gg==" "salt": "e378187547bf2d6f0545a3f441aa4d8a"
}, },
{ {
"user_id":"myuser2", "user_id":"myuser2",
"password_hash":"54bd059dc88e6dd9158306a638215fdce5545eac351fdf2affc8ee94686711c5", "password_hash":"f4d17f300b11e522fd33f497c11b126ef1ea5149c74d2220f9a16dc876d4567b",
"salt": "wF1mavOutYiNvwyYw0PmbQ==" "salt": "6d3f9bd5b54d94b98adbcfe10b6d181f"
} }
] ]