From bb62b44554fc0ec476a09ebc47395ac3fe439019 Mon Sep 17 00:00:00 2001 From: zhouzb Date: Wed, 9 Jun 2021 18:57:08 +0800 Subject: [PATCH] feat(auth): support bcrypt --- .../data/user-credentials.csv | 4 +- .../data/user-credentials.json | 8 +- .../src/emqx_authentication_mnesia.erl | 95 ++++++++++++------- .../test/data/user-credentials.csv | 4 +- .../test/data/user-credentials.json | 8 +- 5 files changed, 71 insertions(+), 48 deletions(-) diff --git a/apps/emqx_authentication/data/user-credentials.csv b/apps/emqx_authentication/data/user-credentials.csv index 108c9f10d..2543d39ca 100644 --- a/apps/emqx_authentication/data/user-credentials.csv +++ b/apps/emqx_authentication/data/user-credentials.csv @@ -1,3 +1,3 @@ user_id,password_hash,salt -myuser3,13de23cc872cf8198797914e95b9ec4e123fd7aaea186aea824452ec0f651a91,DztjMHrbVWmzkF1/dKD/ag== -myuser4,5231a927328f24e7254513819e47277feeb379a724f5e784ddbb09db42d322b7,+kGfV4AH+MR3f30zhoPIkQ== +myuser3,b6c743545a7817ae8c8f624371d5f5f0373234bb0ff36b8ffbf19bce0e06ab75,de1024f462fb83910fd13151bd4bd235 +myuser4,ee68c985a69208b6eda8c6c9b4c7c2d2b15ee2352cdd64a903171710a99182e8,ad773b5be9dd0613fe6c2f4d8c403139 diff --git a/apps/emqx_authentication/data/user-credentials.json b/apps/emqx_authentication/data/user-credentials.json index 1361592da..169122bd2 100644 --- a/apps/emqx_authentication/data/user-credentials.json +++ b/apps/emqx_authentication/data/user-credentials.json @@ -1,12 +1,12 @@ [ { "user_id":"myuser1", - "password_hash":"3e4845e5fc818ac1bfe6a3f77ab665e7721700b5803b6f76def5dce6aacdc42c", - "salt": "LvLGNfMjUJhUpuWIubv4Gg==" + "password_hash":"c5e46903df45e5dc096dc74657610dbee8deaacae656df88a1788f1847390242", + "salt": "e378187547bf2d6f0545a3f441aa4d8a" }, { "user_id":"myuser2", - "password_hash":"54bd059dc88e6dd9158306a638215fdce5545eac351fdf2affc8ee94686711c5", - "salt": "wF1mavOutYiNvwyYw0PmbQ==" + "password_hash":"f4d17f300b11e522fd33f497c11b126ef1ea5149c74d2220f9a16dc876d4567b", + "salt": "6d3f9bd5b54d94b98adbcfe10b6d181f" } ] diff --git a/apps/emqx_authentication/src/emqx_authentication_mnesia.erl b/apps/emqx_authentication/src/emqx_authentication_mnesia.erl index ce2286c28..626ec27b7 100644 --- a/apps/emqx_authentication/src/emqx_authentication_mnesia.erl +++ b/apps/emqx_authentication/src/emqx_authentication_mnesia.erl @@ -45,8 +45,13 @@ password_hash_algorithm => #{ order => 2, type => string, - enum => [<<"plain">>, <<"md5">>, <<"sha">>, <<"sha256">>, <<"sha512">>], + enum => [<<"plain">>, <<"md5">>, <<"sha">>, <<"sha256">>, <<"sha512">>, <<"bcrypt">>], default => <<"sha256">> + }, + salt_rounds => #{ + order => 3, + type => number, + default => 10 } } }). @@ -84,10 +89,13 @@ mnesia(copy) -> ok = ekka_mnesia:copy_table(?TAB, disc_copies). 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}, 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}. update(ChainID, ServiceName, Params, _State) -> @@ -101,12 +109,10 @@ authenticate(ClientInfo = #{password := Password}, case mnesia:dirty_read(?TAB, {UserGroup, UserID}) of [] -> ignore; - [#user_info{password_hash = Hash, salt = Salt}] -> - case Hash =:= emqx_passwd:hash(Algorithm, <>) of - true -> - ok; - false -> - {stop, bad_password} + [#user_info{password_hash = PasswordHash, salt = Salt}] -> + case PasswordHash =:= hash(Algorithm, Password, Salt) of + true -> ok; + false -> {stop, bad_password} end end. @@ -132,13 +138,12 @@ import_users(Filename0, State) -> add_user(#{<<"user_id">> := UserID, <<"password">> := Password}, - #{user_group := UserGroup, - password_hash_algorithm := Algorithm}) -> + #{user_group := UserGroup} = State) -> trans( fun() -> case mnesia:read(?TAB, {UserGroup, UserID}, write) of [] -> - add(UserGroup, UserID, Password, Algorithm), + add(UserID, Password, State), {ok, #{user_id => UserID}}; [_] -> {error, already_exist} @@ -157,15 +162,14 @@ delete_user(UserID, #{user_group := UserGroup}) -> end). update_user(UserID, #{<<"password">> := Password}, - #{user_group := UserGroup, - password_hash_algorithm := Algorithm}) -> + #{user_group := UserGroup} = State) -> trans( fun() -> case mnesia:read(?TAB, {UserGroup, UserID}, write) of [] -> {error, not_found}; [_] -> - add(UserGroup, UserID, Password, Algorithm), + add(UserID, Password, State), {ok, #{user_id => UserID}} end end). @@ -219,10 +223,10 @@ import_users_from_csv(Filename, #{user_group := UserGroup}) -> import(_UserGroup, []) -> ok; import(UserGroup, [#{<<"user_id">> := UserID, - <<"password_hash">> := PasswordHash, - <<"salt">> := Salt} | More]) + <<"password_hash">> := PasswordHash} = UserInfo | More]) 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]) -> {error, bad_format}. @@ -234,9 +238,9 @@ import(UserGroup, File, Seq) -> Fields = binary:split(Line, [<<",">>, <<" ">>, <<"\n">>], [global, trim_all]), case get_user_info_by_seq(Fields, Seq) of {ok, #{user_id := UserID, - password_hash := PasswordHash, - salt := Salt}} -> - import_user(UserGroup, UserID, PasswordHash, Salt), + password_hash := PasswordHash} = UserInfo} -> + Salt = maps:get(salt, UserInfo, <<>>), + insert_user(UserGroup, UserID, PasswordHash, Salt), import(UserGroup, File, Seq); {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) -> {ok, Acc}; +get_user_info_by_seq([], [], #{user_id := _, password_hash := _} = Acc) -> + {ok, Acc}; get_user_info_by_seq(_, [], _) -> {error, bad_format}; 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(_, _, _) -> {error, bad_format}. --compile({inline, [add/4]}). -add(UserGroup, UserID, Password, Algorithm) -> - Salt = case Algorithm of - <<"plain">> -> <<>>; - _ -> crypto:strong_rand_bytes(16) - end, - SaltedPassword = <>, - Credential = #user_info{user_id = {UserGroup, UserID}, - password_hash = emqx_passwd:hash(Algorithm, SaltedPassword), - salt = Salt}, - mnesia:write(?TAB, Credential, write). +-compile({inline, [add/3]}). +add(UserID, Password, #{user_group := UserGroup, + password_hash_algorithm := Algorithm} = State) -> + Salt = gen_salt(State), + PasswordHash = hash(Algorithm, Password, Salt), + case Algorithm of + bcrypt -> insert_user(UserGroup, UserID, PasswordHash); + _ -> insert_user(UserGroup, UserID, PasswordHash, Salt) + end. -import_user(UserGroup, UserID, PasswordHash, Salt) -> - Credential = #user_info{user_id = {UserGroup, UserID}, - password_hash = PasswordHash, - salt = base64:decode(Salt)}, +gen_salt(#{password_hash_algorithm := plain}) -> + <<>>; +gen_salt(#{password_hash_algorithm := bcrypt, + salt_rounds := Rounds}) -> + {ok, Salt} = bcrypt:gen_salt(Rounds), + Salt; +gen_salt(_) -> + <> = 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, <>). + +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). delete_user2(UserInfo) -> diff --git a/apps/emqx_authentication/test/data/user-credentials.csv b/apps/emqx_authentication/test/data/user-credentials.csv index 108c9f10d..2543d39ca 100644 --- a/apps/emqx_authentication/test/data/user-credentials.csv +++ b/apps/emqx_authentication/test/data/user-credentials.csv @@ -1,3 +1,3 @@ user_id,password_hash,salt -myuser3,13de23cc872cf8198797914e95b9ec4e123fd7aaea186aea824452ec0f651a91,DztjMHrbVWmzkF1/dKD/ag== -myuser4,5231a927328f24e7254513819e47277feeb379a724f5e784ddbb09db42d322b7,+kGfV4AH+MR3f30zhoPIkQ== +myuser3,b6c743545a7817ae8c8f624371d5f5f0373234bb0ff36b8ffbf19bce0e06ab75,de1024f462fb83910fd13151bd4bd235 +myuser4,ee68c985a69208b6eda8c6c9b4c7c2d2b15ee2352cdd64a903171710a99182e8,ad773b5be9dd0613fe6c2f4d8c403139 diff --git a/apps/emqx_authentication/test/data/user-credentials.json b/apps/emqx_authentication/test/data/user-credentials.json index 1361592da..169122bd2 100644 --- a/apps/emqx_authentication/test/data/user-credentials.json +++ b/apps/emqx_authentication/test/data/user-credentials.json @@ -1,12 +1,12 @@ [ { "user_id":"myuser1", - "password_hash":"3e4845e5fc818ac1bfe6a3f77ab665e7721700b5803b6f76def5dce6aacdc42c", - "salt": "LvLGNfMjUJhUpuWIubv4Gg==" + "password_hash":"c5e46903df45e5dc096dc74657610dbee8deaacae656df88a1788f1847390242", + "salt": "e378187547bf2d6f0545a3f441aa4d8a" }, { "user_id":"myuser2", - "password_hash":"54bd059dc88e6dd9158306a638215fdce5545eac351fdf2affc8ee94686711c5", - "salt": "wF1mavOutYiNvwyYw0PmbQ==" + "password_hash":"f4d17f300b11e522fd33f497c11b126ef1ea5149c74d2220f9a16dc876d4567b", + "salt": "6d3f9bd5b54d94b98adbcfe10b6d181f" } ]