Configurable password scheme
This gives the administrator control over which algorithm is used to
hash passwords and a separate control over whether this happens on
successful authentication or only at password change time.
closes COUCHDB-2725
diff --git a/src/couch_httpd_auth.erl b/src/couch_httpd_auth.erl
index 01453d8..2db25fb 100644
--- a/src/couch_httpd_auth.erl
+++ b/src/couch_httpd_auth.erl
@@ -372,9 +372,11 @@
maybe_upgrade_password_hash(Req, UserName, Password, UserProps,
AuthModule, AuthCtx) ->
+ Upgrade = config:get("couch_httpd_auth", "upgrade_password_on_auth", "true"),
IsAdmin = lists:member(<<"_admin">>, couch_util:get_value(<<"roles">>, UserProps, [])),
- case {IsAdmin, couch_util:get_value(<<"password_scheme">>, UserProps, <<"simple">>)} of
- {false, <<"simple">>} ->
+ case {IsAdmin, Upgrade,
+ couch_util:get_value(<<"password_scheme">>, UserProps, <<"simple">>)} of
+ {false, "true", <<"simple">>} ->
UserProps2 = proplists:delete(<<"password_sha">>, UserProps),
UserProps3 = [{<<"password">>, Password} | UserProps2],
NewUserDoc = couch_doc:from_json_obj({UserProps3}),
diff --git a/src/couch_passwords.erl b/src/couch_passwords.erl
index 2ab7cc3..e0498b3 100644
--- a/src/couch_passwords.erl
+++ b/src/couch_passwords.erl
@@ -30,6 +30,14 @@
hash_admin_password(ClearPassword) when is_list(ClearPassword) ->
hash_admin_password(?l2b(ClearPassword));
hash_admin_password(ClearPassword) when is_binary(ClearPassword) ->
+ Scheme = config:get("couch_httpd_auth", "password_scheme", "pbkdf2"),
+ hash_admin_password(Scheme, ClearPassword).
+
+hash_admin_password("simple", ClearPassword) ->
+ Salt = couch_uuids:random(),
+ Hash = crypto:sha(<<ClearPassword/binary, Salt/binary>>),
+ ?l2b("-hashed-" ++ couch_util:to_hex(Hash) ++ "," ++ ?b2l(Salt));
+hash_admin_password("pbkdf2", ClearPassword) ->
Iterations = config:get("couch_httpd_auth", "iterations", "10000"),
Salt = couch_uuids:random(),
DerivedKey = couch_passwords:pbkdf2(couch_util:to_binary(ClearPassword),
diff --git a/src/couch_users_db.erl b/src/couch_users_db.erl
index ade1955..4992a61 100644
--- a/src/couch_users_db.erl
+++ b/src/couch_users_db.erl
@@ -20,6 +20,8 @@
-define(PASSWORD, <<"password">>).
-define(DERIVED_KEY, <<"derived_key">>).
-define(PASSWORD_SCHEME, <<"password_scheme">>).
+-define(SIMPLE, <<"simple">>).
+-define(PASSWORD_SHA, <<"password_sha">>).
-define(PBKDF2, <<"pbkdf2">>).
-define(ITERATIONS, <<"iterations">>).
-define(SALT, <<"salt">>).
@@ -57,12 +59,21 @@
% newDoc.salt = salt
% newDoc.password = null
save_doc(#doc{body={Body}} = Doc) ->
- case couch_util:get_value(?PASSWORD, Body) of
- null -> % server admins don't have a user-db password entry
+ Scheme = config:get("couch_httpd_auth", "password_scheme", "pbkdf2"),
+ case {couch_util:get_value(?PASSWORD, Body), Scheme} of
+ {null, _} -> % server admins don't have a user-db password entry
Doc;
- undefined ->
+ {undefined, _} ->
Doc;
- ClearPassword ->
+ {ClearPassword, "simple"} ->
+ Salt = couch_uuids:random(),
+ PasswordSha = couch_passwords:simple(ClearPassword, Salt),
+ Body0 = ?replace(Body, ?PASSWORD_SCHEME, ?SIMPLE),
+ Body1 = ?replace(Body0, ?SALT, Salt),
+ Body2 = ?replace(Body1, ?PASSWORD_SHA, PasswordSha),
+ Body3 = proplists:delete(?PASSWORD, Body2),
+ Doc#doc{body={Body3}};
+ {ClearPassword, "pbkdf2"} ->
Iterations = list_to_integer(config:get("couch_httpd_auth", "iterations", "1000")),
Salt = couch_uuids:random(),
DerivedKey = couch_passwords:pbkdf2(ClearPassword, Salt, Iterations),
@@ -71,7 +82,10 @@
Body2 = ?replace(Body1, ?DERIVED_KEY, DerivedKey),
Body3 = ?replace(Body2, ?SALT, Salt),
Body4 = proplists:delete(?PASSWORD, Body3),
- Doc#doc{body={Body4}}
+ Doc#doc{body={Body4}};
+ {_ClearPassword, Scheme} ->
+ couch_log:warn("Configured password scheme '~p' not recognised.", [Scheme]),
+ Doc
end.
% If the doc is a design doc