]> granicus.if.org Git - postgresql/commitdiff
Change the on-disk format of SCRAM verifiers to conform to RFC 5803.
authorHeikki Linnakangas <heikki.linnakangas@iki.fi>
Fri, 21 Apr 2017 19:51:57 +0000 (22:51 +0300)
committerHeikki Linnakangas <heikki.linnakangas@iki.fi>
Fri, 21 Apr 2017 19:51:57 +0000 (22:51 +0300)
It doesn't make any immediate difference to PostgreSQL, but might as well
follow the standard, since one exists. (I looked at RFC 5803 earlier, but
didn't fully understand it back then.)

The new format uses Base64 instead of hex to encode StoredKey and
ServerKey, which makes the verifiers slightly smaller. Using the same
encoding for the salt and the keys also means that you only need one
encoder/decoder instead of two. Although we have code in the backend to
do both, we are talking about teaching libpq how to create SCRAM verifiers
for PQencodePassword(), and libpq doesn't currently have any code for hex
encoding.

Bump catversion, because this renders any existing SCRAM verifiers in
pg_authid invalid.

Discussion: https://www.postgresql.org/message-id/351ba574-85ea-d9b8-9689-8c928dd0955d@iki.fi

doc/src/sgml/catalogs.sgml
src/backend/libpq/auth-scram.c
src/backend/libpq/crypt.c
src/include/catalog/catversion.h
src/test/regress/expected/password.out
src/test/regress/sql/password.sql

index ed74704b2adebf6cb7e5048f64855e72febca378..787fcbd51ac125bc4db28d4c48b7a9f890ccf8c9 100644 (file)
    32-character hexadecimal MD5 hash. The MD5 hash will be of the user's
    password concatenated to their user name. For example, if user
    <literal>joe</> has password <literal>xyzzy</>, <productname>PostgreSQL</>
-   will store the md5 hash of <literal>xyzzyjoe</>.  If the password is
-   encrypted with SCRAM-SHA-256, it consists of 5 fields separated by colons.
-   The first field is the constant <literal>scram-sha-256</literal>, to
-   identify the password as a SCRAM-SHA-256 verifier. The second field is a
-   salt, Base64-encoded, and the third field is the number of iterations used
-   to generate the password.  The fourth field and fifth field are the stored
-   key and server key, respectively, in hexadecimal format. A password that
-   does not follow either of those formats is assumed to be unencrypted.
+   will store the md5 hash of <literal>xyzzyjoe</>.
+  </para>
+
+  <para>
+   If the password is encrypted with SCRAM-SHA-256, it has the format:
+<synopsis>
+SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</>:<replaceable>&lt;salt&gt;</>$<replaceable>&lt;StoredKey&gt;</>:<replaceable>&lt;ServerKey&gt;</>
+</synopsis>
+   where <replaceable>salt</>, <replaceable>StoredKey</> and
+   <replaceable>ServerKey</> are in Base64 encoded format. This format is
+   the same as that specified by RFC 5803.
+  </para>
+
+  <para>
+    A password that does not follow either of those formats is assumed to be
+    unencrypted.
   </para>
  </sect1>
 
index 76c502d415da62d67c416a84312bcb1dc550b087..16bea446e37dae75750b41faa360337fd91234be 100644 (file)
@@ -5,6 +5,7 @@
  *
  * See the following RFCs for more details:
  * - RFC 5802: https://tools.ietf.org/html/rfc5802
+ * - RFC 5803: https://tools.ietf.org/html/rfc5803
  * - RFC 7677: https://tools.ietf.org/html/rfc7677
  *
  * Here are some differences:
@@ -19,7 +20,7 @@
  * - Channel binding is not supported yet.
  *
  *
- * The password stored in pg_authid consists of the salt, iteration count,
+ * The password stored in pg_authid consists of the iteration count, salt,
  * StoredKey and ServerKey.
  *
  * SASLprep usage
@@ -111,8 +112,8 @@ typedef struct
 
        const char *username;           /* username from startup packet */
 
-       char       *salt;                       /* base64-encoded */
        int                     iterations;
+       char       *salt;                       /* base64-encoded */
        uint8           StoredKey[SCRAM_KEY_LEN];
        uint8           ServerKey[SCRAM_KEY_LEN];
 
@@ -146,10 +147,10 @@ static char *build_server_first_message(scram_state *state);
 static char *build_server_final_message(scram_state *state);
 static bool verify_client_proof(scram_state *state);
 static bool verify_final_nonce(scram_state *state);
-static bool parse_scram_verifier(const char *verifier, char **salt,
-                                        int *iterations, uint8 *stored_key, uint8 *server_key);
-static void mock_scram_verifier(const char *username, char **salt, int *iterations,
-                                       uint8 *stored_key, uint8 *server_key);
+static bool parse_scram_verifier(const char *verifier, int *iterations,
+                                        char **salt, uint8 *stored_key, uint8 *server_key);
+static void mock_scram_verifier(const char *username, int *iterations,
+                                       char **salt, uint8 *stored_key, uint8 *server_key);
 static bool is_scram_printable(char *p);
 static char *sanitize_char(char c);
 static char *scram_MockSalt(const char *username);
@@ -185,7 +186,7 @@ pg_be_scram_init(const char *username, const char *shadow_pass)
 
                if (password_type == PASSWORD_TYPE_SCRAM_SHA_256)
                {
-                       if (parse_scram_verifier(shadow_pass, &state->salt, &state->iterations,
+                       if (parse_scram_verifier(shadow_pass, &state->iterations, &state->salt,
                                                                         state->StoredKey, state->ServerKey))
                                got_verifier = true;
                        else
@@ -208,7 +209,7 @@ pg_be_scram_init(const char *username, const char *shadow_pass)
 
                        verifier = scram_build_verifier(username, shadow_pass, 0);
 
-                       (void) parse_scram_verifier(verifier, &state->salt, &state->iterations,
+                       (void) parse_scram_verifier(verifier, &state->iterations, &state->salt,
                                                                                state->StoredKey, state->ServerKey);
                        pfree(verifier);
 
@@ -243,7 +244,7 @@ pg_be_scram_init(const char *username, const char *shadow_pass)
         */
        if (!got_verifier)
        {
-               mock_scram_verifier(username, &state->salt, &state->iterations,
+               mock_scram_verifier(username, &state->iterations, &state->salt,
                                                        state->StoredKey, state->ServerKey);
                state->doomed = true;
        }
@@ -393,14 +394,15 @@ char *
 scram_build_verifier(const char *username, const char *password,
                                         int iterations)
 {
-       uint8           keybuf[SCRAM_KEY_LEN + 1];
-       char            storedkey_hex[SCRAM_KEY_LEN * 2 + 1];
-       char            serverkey_hex[SCRAM_KEY_LEN * 2 + 1];
-       char            salt[SCRAM_SALT_LEN];
-       char       *encoded_salt;
-       int                     encoded_len;
        char       *prep_password = NULL;
        pg_saslprep_rc rc;
+       char            saltbuf[SCRAM_SALT_LEN];
+       uint8           keybuf[SCRAM_KEY_LEN];
+       char       *encoded_salt;
+       char       *encoded_storedkey;
+       char       *encoded_serverkey;
+       int                     encoded_len;
+       char       *result;
 
        /*
         * Normalize the password with SASLprep.  If that doesn't work, because
@@ -414,7 +416,8 @@ scram_build_verifier(const char *username, const char *password,
        if (iterations <= 0)
                iterations = SCRAM_ITERATIONS_DEFAULT;
 
-       if (!pg_backend_random(salt, SCRAM_SALT_LEN))
+       /* Generate salt, and encode it in base64 */
+       if (!pg_backend_random(saltbuf, SCRAM_SALT_LEN))
        {
                ereport(LOG,
                                (errcode(ERRCODE_INTERNAL_ERROR),
@@ -423,26 +426,38 @@ scram_build_verifier(const char *username, const char *password,
        }
 
        encoded_salt = palloc(pg_b64_enc_len(SCRAM_SALT_LEN) + 1);
-       encoded_len = pg_b64_encode(salt, SCRAM_SALT_LEN, encoded_salt);
+       encoded_len = pg_b64_encode(saltbuf, SCRAM_SALT_LEN, encoded_salt);
        encoded_salt[encoded_len] = '\0';
 
-       /* Calculate StoredKey, and encode it in hex */
-       scram_ClientOrServerKey(password, salt, SCRAM_SALT_LEN,
+       /* Calculate StoredKey, and encode it in base64 */
+       scram_ClientOrServerKey(password, saltbuf, SCRAM_SALT_LEN,
                                                        iterations, SCRAM_CLIENT_KEY_NAME, keybuf);
        scram_H(keybuf, SCRAM_KEY_LEN, keybuf);         /* StoredKey */
-       (void) hex_encode((const char *) keybuf, SCRAM_KEY_LEN, storedkey_hex);
-       storedkey_hex[SCRAM_KEY_LEN * 2] = '\0';
+
+       encoded_storedkey = palloc(pg_b64_enc_len(SCRAM_KEY_LEN) + 1);
+       encoded_len = pg_b64_encode((const char *) keybuf, SCRAM_KEY_LEN,
+                                                               encoded_storedkey);
+       encoded_storedkey[encoded_len] = '\0';
 
        /* And same for ServerKey */
-       scram_ClientOrServerKey(password, salt, SCRAM_SALT_LEN, iterations,
+       scram_ClientOrServerKey(password, saltbuf, SCRAM_SALT_LEN, iterations,
                                                        SCRAM_SERVER_KEY_NAME, keybuf);
-       (void) hex_encode((const char *) keybuf, SCRAM_KEY_LEN, serverkey_hex);
-       serverkey_hex[SCRAM_KEY_LEN * 2] = '\0';
+
+       encoded_serverkey = palloc(pg_b64_enc_len(SCRAM_KEY_LEN) + 1);
+       encoded_len = pg_b64_encode((const char *) keybuf, SCRAM_KEY_LEN,
+                                                               encoded_serverkey);
+       encoded_serverkey[encoded_len] = '\0';
+
+       result = psprintf("SCRAM-SHA-256$%d:%s$%s:%s", iterations, encoded_salt,
+                                         encoded_storedkey, encoded_serverkey);
 
        if (prep_password)
                pfree(prep_password);
+       pfree(encoded_salt);
+       pfree(encoded_storedkey);
+       pfree(encoded_serverkey);
 
-       return psprintf("scram-sha-256:%s:%d:%s:%s", encoded_salt, iterations, storedkey_hex, serverkey_hex);
+       return result;
 }
 
 /*
@@ -464,7 +479,7 @@ scram_verify_plain_password(const char *username, const char *password,
        char       *prep_password = NULL;
        pg_saslprep_rc rc;
 
-       if (!parse_scram_verifier(verifier, &encoded_salt, &iterations,
+       if (!parse_scram_verifier(verifier, &iterations, &encoded_salt,
                                                          stored_key, server_key))
        {
                /*
@@ -509,13 +524,14 @@ scram_verify_plain_password(const char *username, const char *password,
 bool
 is_scram_verifier(const char *verifier)
 {
-       char       *salt = NULL;
        int                     iterations;
+       char       *salt = NULL;
        uint8           stored_key[SCRAM_KEY_LEN];
        uint8           server_key[SCRAM_KEY_LEN];
        bool            result;
 
-       result = parse_scram_verifier(verifier, &salt, &iterations, stored_key, server_key);
+       result = parse_scram_verifier(verifier, &iterations, &salt,
+                                                                 stored_key, server_key);
        if (salt)
                pfree(salt);
 
@@ -529,60 +545,82 @@ is_scram_verifier(const char *verifier)
  * Returns true if the SCRAM verifier has been parsed, and false otherwise.
  */
 static bool
-parse_scram_verifier(const char *verifier, char **salt, int *iterations,
+parse_scram_verifier(const char *verifier, int *iterations, char **salt,
                                         uint8 *stored_key, uint8 *server_key)
 {
        char       *v;
        char       *p;
+       char       *scheme_str;
+       char       *salt_str;
+       char       *iterations_str;
+       char       *storedkey_str;
+       char       *serverkey_str;
+       int                     decoded_len;
+       char       *decoded_salt_buf;
 
        /*
         * The verifier is of form:
         *
-        * scram-sha-256:<salt>:<iterations>:<storedkey>:<serverkey>
+        * SCRAM-SHA-256$<iterations>:<salt>$<storedkey>:<serverkey>
         */
-       if (strncmp(verifier, "scram-sha-256:", strlen("scram-sha-256:")) != 0)
-               return false;
-
-       v = pstrdup(verifier + strlen("scram-sha-256:"));
-
-       /* salt */
-       if ((p = strtok(v, ":")) == NULL)
+       v = pstrdup(verifier);
+       if ((scheme_str = strtok(v, "$")) == NULL)
+               goto invalid_verifier;
+       if ((iterations_str = strtok(NULL, ":")) == NULL)
+               goto invalid_verifier;
+       if ((salt_str = strtok(NULL, "$")) == NULL)
+               goto invalid_verifier;
+       if ((storedkey_str = strtok(NULL, ":")) == NULL)
+               goto invalid_verifier;
+       if ((serverkey_str = strtok(NULL, "")) == NULL)
                goto invalid_verifier;
-       *salt = pstrdup(p);
 
-       /* iterations */
-       if ((p = strtok(NULL, ":")) == NULL)
+       /* Parse the fields */
+       if (strcmp(scheme_str, "SCRAM-SHA-256") != 0)
                goto invalid_verifier;
+
        errno = 0;
-       *iterations = strtol(p, &p, 10);
+       *iterations = strtol(iterations_str, &p, 10);
        if (*p || errno != 0)
                goto invalid_verifier;
 
-       /* storedkey */
-       if ((p = strtok(NULL, ":")) == NULL)
-               goto invalid_verifier;
-       if (strlen(p) != SCRAM_KEY_LEN * 2)
+       /*
+        * Verify that the salt is in Base64-encoded format, by decoding it,
+        * although we return the encoded version to the caller.
+        */
+       decoded_salt_buf = palloc(pg_b64_dec_len(strlen(salt_str)));
+       decoded_len = pg_b64_decode(salt_str, strlen(salt_str), decoded_salt_buf);
+       if (decoded_len < 0)
                goto invalid_verifier;
+       *salt = pstrdup(salt_str);
 
-       hex_decode(p, SCRAM_KEY_LEN * 2, (char *) stored_key);
+       /*
+        * Decode StoredKey and ServerKey.
+        */
+       if (pg_b64_dec_len(strlen(storedkey_str) != SCRAM_KEY_LEN))
+               goto invalid_verifier;
+       decoded_len = pg_b64_decode(storedkey_str, strlen(storedkey_str),
+                                                               (char *) stored_key);
+       if (decoded_len != SCRAM_KEY_LEN)
+               goto invalid_verifier;
 
-       /* serverkey */
-       if ((p = strtok(NULL, ":")) == NULL)
+       if (pg_b64_dec_len(strlen(serverkey_str) != SCRAM_KEY_LEN))
                goto invalid_verifier;
-       if (strlen(p) != SCRAM_KEY_LEN * 2)
+       decoded_len = pg_b64_decode(serverkey_str, strlen(serverkey_str),
+                                                               (char *) server_key);
+       if (decoded_len != SCRAM_KEY_LEN)
                goto invalid_verifier;
-       hex_decode(p, SCRAM_KEY_LEN * 2, (char *) server_key);
 
-       pfree(v);
        return true;
 
 invalid_verifier:
        pfree(v);
+       *salt = NULL;
        return false;
 }
 
 static void
-mock_scram_verifier(const char *username, char **salt, int *iterations,
+mock_scram_verifier(const char *username, int *iterations, char **salt,
                                        uint8 *stored_key, uint8 *server_key)
 {
        char       *raw_salt;
index 03ef3cc65228646c7b5d25b2f40ade1f971ead60..d0030f2b6d80ae4953d0adebc6b8fcf06f8dbf07 100644 (file)
@@ -100,7 +100,7 @@ get_password_type(const char *shadow_pass)
 {
        if (strncmp(shadow_pass, "md5", 3) == 0 && strlen(shadow_pass) == MD5_PASSWD_LEN)
                return PASSWORD_TYPE_MD5;
-       if (strncmp(shadow_pass, "scram-sha-256:", strlen("scram-sha-256:")) == 0)
+       if (strncmp(shadow_pass, "SCRAM-SHA-256$", strlen("SCRAM-SHA-256$")) == 0)
                return PASSWORD_TYPE_SCRAM_SHA_256;
        return PASSWORD_TYPE_PLAINTEXT;
 }
index ab92fd88ed8d2083e5a1055874b541b8c9481fca..6cc9e30ec241bc37d2face2be6643160252d94ff 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     201704171
+#define CATALOG_VERSION_NO     201704211
 
 #endif
index 676b3e6ff3f75b63ad4c51a19ec49a68fa895c49..9ec5a52bba4e04a3cb576f762e6b90b311c01f5d 100644 (file)
@@ -23,11 +23,11 @@ CREATE ROLE regress_passwd5 PASSWORD NULL;
 -- check list of created entries
 --
 -- The scram verifier will look something like:
--- scram-sha-256:E4HxLGtnRzsYwg==:4096:5ebc825510cb7862efd87dfa638d8337179e6913a724441dc9e888a856fbc10c:e966b1c72fad89d69aaebb156eae04edc9581286f92207c044711e79cd461bee
+-- SCRAM-SHA-256$4096:E4HxLGtnRzsYwg==$6YtlR4t69SguDiwFvbVgVZtuz6gpJQQqUMZ7IQJK5yI=:ps75jrHeYU4lXCcXI4O8oIdJ3eO8o2jirjruw9phBTo=
 --
 -- Since the salt is random, the exact value stored will be different on every test
 -- run. Use a regular expression to mask the changing parts.
-SELECT rolname, regexp_replace(rolpassword, '(scram-sha-256):([a-zA-Z0-9+/]+==):(\d+):(\w+):(\w+)', '\1:<salt>:\3:<storedkey>:<serverkey>') as rolpassword_masked
+SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/]+==)\$([a-zA-Z0-9+/]+=):([a-zA-Z0-9+/]+=)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
     FROM pg_authid
     WHERE rolname LIKE 'regress_passwd%'
     ORDER BY rolname, rolpassword;
@@ -36,7 +36,7 @@ SELECT rolname, regexp_replace(rolpassword, '(scram-sha-256):([a-zA-Z0-9+/]+==):
  regress_passwd1 | role_pwd1
  regress_passwd2 | md54044304ba511dd062133eb5b4b84a2a3
  regress_passwd3 | md50e5699b6911d87f17a08b8d76a21e8b8
- regress_passwd4 | scram-sha-256:<salt>:4096:<storedkey>:<serverkey>
+ regress_passwd4 | SCRAM-SHA-256$4096:<salt>$<storedkey>:<serverkey>
  regress_passwd5 | 
 (5 rows)
 
@@ -59,11 +59,11 @@ ALTER ROLE regress_passwd1 UNENCRYPTED PASSWORD 'foo'; -- unencrypted
 ALTER ROLE regress_passwd2 UNENCRYPTED PASSWORD 'md5dfa155cadd5f4ad57860162f3fab9cdb'; -- encrypted with MD5
 SET password_encryption = 'md5';
 ALTER ROLE regress_passwd3 ENCRYPTED PASSWORD 'foo'; -- encrypted with MD5
-ALTER ROLE regress_passwd4 ENCRYPTED PASSWORD 'scram-sha-256:VLK4RMaQLCvNtQ==:4096:3ded2376f7aafa93b1bdbd71bcc18b7d6ee50ed018029cc583d152ef3fc7d430:a6dd36dfc94c181956a6ae95f05e01b1864f0a22a2657d1de4ba84d2a24dc438'; -- client-supplied SCRAM verifier, use as it is
+ALTER ROLE regress_passwd4 ENCRYPTED PASSWORD 'SCRAM-SHA-256$4096:VLK4RMaQLCvNtQ==$6YtlR4t69SguDiwFvbVgVZtuz6gpJQQqUMZ7IQJK5yI=:ps75jrHeYU4lXCcXI4O8oIdJ3eO8o2jirjruw9phBTo='; -- client-supplied SCRAM verifier, use as it is
 SET password_encryption = 'scram-sha-256';
 ALTER ROLE  regress_passwd5 ENCRYPTED PASSWORD 'foo'; -- create SCRAM verifier
 CREATE ROLE regress_passwd6 ENCRYPTED PASSWORD 'md53725413363ab045e20521bf36b8d8d7f'; -- encrypted with MD5, use as it is
-SELECT rolname, regexp_replace(rolpassword, '(scram-sha-256):([a-zA-Z0-9+/]+==):(\d+):(\w+):(\w+)', '\1:<salt>:\3:<storedkey>:<serverkey>') as rolpassword_masked
+SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/]+==)\$([a-zA-Z0-9+/]+=):([a-zA-Z0-9+/]+=)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
     FROM pg_authid
     WHERE rolname LIKE 'regress_passwd%'
     ORDER BY rolname, rolpassword;
@@ -72,8 +72,8 @@ SELECT rolname, regexp_replace(rolpassword, '(scram-sha-256):([a-zA-Z0-9+/]+==):
  regress_passwd1 | foo
  regress_passwd2 | md5dfa155cadd5f4ad57860162f3fab9cdb
  regress_passwd3 | md5530de4c298af94b3b9f7d20305d2a1bf
- regress_passwd4 | scram-sha-256:<salt>:4096:<storedkey>:<serverkey>
- regress_passwd5 | scram-sha-256:<salt>:4096:<storedkey>:<serverkey>
+ regress_passwd4 | SCRAM-SHA-256$4096:<salt>$<storedkey>:<serverkey>
+ regress_passwd5 | SCRAM-SHA-256$4096:<salt>$<storedkey>:<serverkey>
  regress_passwd6 | md53725413363ab045e20521bf36b8d8d7f
 (6 rows)
 
index 95557e45660eafb3de9d501ff87c297e68f7a65b..1e022dbf2d6d217568fba521b42a2ca6f4c6ed9e 100644 (file)
@@ -24,11 +24,11 @@ CREATE ROLE regress_passwd5 PASSWORD NULL;
 -- check list of created entries
 --
 -- The scram verifier will look something like:
--- scram-sha-256:E4HxLGtnRzsYwg==:4096:5ebc825510cb7862efd87dfa638d8337179e6913a724441dc9e888a856fbc10c:e966b1c72fad89d69aaebb156eae04edc9581286f92207c044711e79cd461bee
+-- SCRAM-SHA-256$4096:E4HxLGtnRzsYwg==$6YtlR4t69SguDiwFvbVgVZtuz6gpJQQqUMZ7IQJK5yI=:ps75jrHeYU4lXCcXI4O8oIdJ3eO8o2jirjruw9phBTo=
 --
 -- Since the salt is random, the exact value stored will be different on every test
 -- run. Use a regular expression to mask the changing parts.
-SELECT rolname, regexp_replace(rolpassword, '(scram-sha-256):([a-zA-Z0-9+/]+==):(\d+):(\w+):(\w+)', '\1:<salt>:\3:<storedkey>:<serverkey>') as rolpassword_masked
+SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/]+==)\$([a-zA-Z0-9+/]+=):([a-zA-Z0-9+/]+=)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
     FROM pg_authid
     WHERE rolname LIKE 'regress_passwd%'
     ORDER BY rolname, rolpassword;
@@ -48,13 +48,13 @@ ALTER ROLE regress_passwd2 UNENCRYPTED PASSWORD 'md5dfa155cadd5f4ad57860162f3fab
 SET password_encryption = 'md5';
 ALTER ROLE regress_passwd3 ENCRYPTED PASSWORD 'foo'; -- encrypted with MD5
 
-ALTER ROLE regress_passwd4 ENCRYPTED PASSWORD 'scram-sha-256:VLK4RMaQLCvNtQ==:4096:3ded2376f7aafa93b1bdbd71bcc18b7d6ee50ed018029cc583d152ef3fc7d430:a6dd36dfc94c181956a6ae95f05e01b1864f0a22a2657d1de4ba84d2a24dc438'; -- client-supplied SCRAM verifier, use as it is
+ALTER ROLE regress_passwd4 ENCRYPTED PASSWORD 'SCRAM-SHA-256$4096:VLK4RMaQLCvNtQ==$6YtlR4t69SguDiwFvbVgVZtuz6gpJQQqUMZ7IQJK5yI=:ps75jrHeYU4lXCcXI4O8oIdJ3eO8o2jirjruw9phBTo='; -- client-supplied SCRAM verifier, use as it is
 
 SET password_encryption = 'scram-sha-256';
 ALTER ROLE  regress_passwd5 ENCRYPTED PASSWORD 'foo'; -- create SCRAM verifier
 CREATE ROLE regress_passwd6 ENCRYPTED PASSWORD 'md53725413363ab045e20521bf36b8d8d7f'; -- encrypted with MD5, use as it is
 
-SELECT rolname, regexp_replace(rolpassword, '(scram-sha-256):([a-zA-Z0-9+/]+==):(\d+):(\w+):(\w+)', '\1:<salt>:\3:<storedkey>:<serverkey>') as rolpassword_masked
+SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/]+==)\$([a-zA-Z0-9+/]+=):([a-zA-Z0-9+/]+=)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
     FROM pg_authid
     WHERE rolname LIKE 'regress_passwd%'
     ORDER BY rolname, rolpassword;