diff --git a/changelogs/unreleased/gh-8967-upgrade-password-sync.md b/changelogs/unreleased/gh-8967-upgrade-password-sync.md
new file mode 100644
index 0000000000000000000000000000000000000000..4f29644619def3ca750838f730a095895840165e
--- /dev/null
+++ b/changelogs/unreleased/gh-8967-upgrade-password-sync.md
@@ -0,0 +1,7 @@
+## feature/config
+
+* Implemented a full password support in the `config.credentials` schema,
+  including a password setting, updating and removal for the `chap-sha1`
+  auth type (supported by both Tarantool Community Edition and Tarantool
+  Enterprise Edition) and the `pap-sha256` (just for Enterprise Edition
+  where it is available) (gh-8967).
diff --git a/src/box/lua/config/applier/credentials.lua b/src/box/lua/config/applier/credentials.lua
index dae96c12fe03fc0b72cefa566faef8f148c786c6..e9ce2832aa2f790a24e05f84c4e6d780be6d64e4 100644
--- a/src/box/lua/config/applier/credentials.lua
+++ b/src/box/lua/config/applier/credentials.lua
@@ -1,4 +1,5 @@
 local log = require('internal.config.utils.log')
+local digest = require('digest')
 
 --[[
 This intermediate representation is a formatted set of
@@ -371,27 +372,77 @@ end
 
 local function set_password(user_name, password)
     if password == nil then
-        if user_name ~= 'guest' then
-            log.verbose('credentials.apply: remove password for user %q',
-                user_name)
-            -- TODO: Check for hashes and if absent remove the password.
-        end
-    else
         if user_name == 'guest' then
-            error('Setting a password for the guest user has no effect')
+            -- Guest can't have a password, so this is valid and there
+            -- is nothing to do.
+            return
+        end
+
+        local auth_def = box.space._user.index.name:get({user_name})[5]
+        if next(auth_def) == nil then
+            -- No password is currently set, there is nothing to do.
+            log.verbose('credentials.apply: user %q already has no password',
+                        user_name)
+            return
         end
-        -- TODO: Check if the password can be hashed in somewhere other then
-        --       'chap-sha1' or if the select{user_name} may return table of
-        --       a different shape.
-        local stored_user_def = box.space._user.index.name:get({user_name})
-        local stored_hash = stored_user_def[5]['chap-sha1']
-        local given_hash = box.schema.user.password(password)
-        if given_hash == stored_hash then
+
+        log.verbose('credentials.apply: remove password for user %q', user_name)
+        -- No password is set, so remove it for the user.
+        -- There is no handy function for it, so remove it directly in
+        -- system space _user. Users are described in the following way:
+        --
+        -- - [32, 1, 'myusername', 'user', {'chap-sha1': '<password_hash>'},
+        --          [], 1692279984]
+        --
+        -- The command below overrides with empty map the fifth tuple field,
+        -- containing hash (with auth_type == 'chap-sha1' and hash with salt
+        -- with auth_type == 'pap-sha256').
+        box.space._user.index.name:update(user_name,
+            {{'=', 5, setmetatable({}, {__serialize = 'map'})}})
+
+        return
+    end
+
+    if user_name == 'guest' then
+        error('Setting a password for the guest user is not allowed')
+    end
+
+    local auth_def = box.space._user.index.name:get({user_name})[5]
+    if next(auth_def) == nil then
+        -- No password is currently set for the user, just set a new one.
+        log.verbose('credentials.apply: set a password for user %q', user_name)
+        box.schema.user.passwd(user_name, password)
+        return
+    end
+
+    local auth_type = auth_def['chap-sha1'] and 'chap-sha1' or 'pap-sha256'
+
+    if auth_type == 'chap-sha1' then
+        local current_hash = auth_def['chap-sha1']
+
+        local new_hash = box.schema.user.password(password)
+        if new_hash == current_hash then
+            log.verbose('credentials.apply: a password is already set ' ..
+                        'for user %q', user_name)
+        else
+            log.verbose('credentials.apply: set a password for user %q',
+                        user_name)
+            box.schema.user.passwd(user_name, password)
+        end
+    else
+        assert(auth_def['pap-sha256'])
+        local current_salt = auth_def['pap-sha256'][1]
+        local current_hash = auth_def['pap-sha256'][2]
+
+        local new_hash = digest.sha256(current_salt .. password)
+        if new_hash == current_hash then
             log.verbose('credentials.apply: a password is already set ' ..
-                'for user %q', user_name)
+                        'for user %q', user_name)
         else
             log.verbose('credentials.apply: set a password for user %q',
-                user_name)
+                        user_name)
+            -- Note: passwd() generated new random salt, it will be different
+            -- from current_salt.
             box.schema.user.passwd(user_name, password)
         end
     end
@@ -456,5 +507,6 @@ return {
         privileges_subtract = privileges_subtract,
         privileges_add_defaults = privileges_add_defaults,
         sync_privileges = sync_privileges,
+        set_password = set_password,
     },
 }
diff --git a/test/config-luatest/credentials_applier_test.lua b/test/config-luatest/credentials_applier_test.lua
index a55d1c06b532c8add6b12189b72e9764387d3ba7..9990b5a98e9847516567e756cb1fc6f58f09166a 100644
--- a/test/config-luatest/credentials_applier_test.lua
+++ b/test/config-luatest/credentials_applier_test.lua
@@ -478,3 +478,54 @@ g.test_sync_privileges = function(g)
 
     child:close()
 end
+
+g.test_set_password = function(g)
+
+    local auth_types = {
+        'chap-sha1',
+        'pap-sha256',
+    }
+
+    for _, auth_type in ipairs(auth_types) do
+        if auth_type == 'pap-sha256' then
+            t.tarantool.skip_if_not_enterprise()
+        end
+        local child = it.new()
+        local dir = treegen.prepare_directory(g, {}, {})
+        local socket = "unix/:./test_socket.iproto"
+
+        child:roundtrip(("box.cfg{work_dir = %q, listen = %q, auth_type = %q}")
+                        :format(dir, socket, auth_type))
+        child:roundtrip("nb = require('net.box')")
+
+        local name = "myuser"
+        child:roundtrip(("myuser = %q"):format(name))
+        child:roundtrip("box.schema.user.create(myuser)")
+
+        child:roundtrip("set_password = require('internal.config.applier." ..
+                        "credentials')._internal.set_password")
+
+        child:roundtrip("set_password(myuser, 'password1')")
+
+        child:roundtrip(("nb.connect(%q, {user = myuser, " ..
+                         "password = 'password1'}).state"):format(socket),
+                        "active")
+
+        child:roundtrip("set_password(myuser, 'password2')")
+
+        child:roundtrip(("nb.connect(%q, {user = myuser, " ..
+                         "password = 'password1'}).state"):format(socket),
+                        "error")
+
+        child:roundtrip(("nb.connect(%q, {user = myuser, " ..
+                         "password = 'password2'}).state"):format(socket),
+                        "active")
+
+        child:roundtrip("set_password(myuser, nil)")
+
+        child:roundtrip(("nb.connect(%q, {user = myuser, " ..
+                         "password = 'password2'}).state"):format(socket),
+                        "error")
+        child:close()
+    end
+end