<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: supi sula</title>
    <description>The latest articles on DEV Community by supi sula (@supi_sula_20573ba512b582d).</description>
    <link>https://dev.to/supi_sula_20573ba512b582d</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3204796%2F87fe229a-e4ff-4285-9b61-64a5c3e97088.png</url>
      <title>DEV Community: supi sula</title>
      <link>https://dev.to/supi_sula_20573ba512b582d</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/supi_sula_20573ba512b582d"/>
    <language>en</language>
    <item>
      <title>SCRAM Authentication in PostgreSQL and SCRAM Pass-Through Authentication</title>
      <dc:creator>supi sula</dc:creator>
      <pubDate>Mon, 26 May 2025 10:36:36 +0000</pubDate>
      <link>https://dev.to/supi_sula_20573ba512b582d/scram-authentication-in-postgresql-and-scram-pass-through-authentication-afh</link>
      <guid>https://dev.to/supi_sula_20573ba512b582d/scram-authentication-in-postgresql-and-scram-pass-through-authentication-afh</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;PostgreSQL supports multiple client authentication methods. In addition to external authentication providers, it also supports password-based authentication using credentials stored in the database.&lt;/p&gt;

&lt;p&gt;Among the password-based methods, the recommended one is scram-sha-256, which is a form of SCRAM authentication as discussed in the &lt;a href="https://dev.to/supi_sula_20573ba512b582d/why-does-scram-authentication-take-this-form-24o9"&gt;previous article&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This article introduces how SCRAM authentication works in PostgreSQL, and explains a new feature planned for PostgreSQL 18: SCRAM pass-through authentication.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2025-05-31 Added usage of SCRAM pass-through authentication&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;(This article is a translation of a &lt;a href="https://dev-supisula.hatenablog.com/entry/scram-authentication-in-postgresql-and-scram-passthrough" rel="noopener noreferrer"&gt;Japanese article&lt;/a&gt; written by the author themselves.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Using SCRAM Authentication in PostgreSQL
&lt;/h2&gt;

&lt;p&gt;SCRAM authentication is triggered in PostgreSQL when all of the following conditions are met:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The password for the user is stored in &lt;code&gt;scram-sha-256&lt;/code&gt; format,&lt;/li&gt;
&lt;li&gt;The user has registered a password (credential) in the database, and&lt;/li&gt;
&lt;li&gt;The authentication method selected in &lt;code&gt;pg_hba.conf&lt;/code&gt; is either &lt;code&gt;scram-sha-256&lt;/code&gt; or &lt;code&gt;md5&lt;/code&gt;*1.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Password Storage Format
&lt;/h3&gt;

&lt;p&gt;Database user passwords are stored in the &lt;code&gt;rolpassword&lt;/code&gt; column of the system catalog &lt;a href="https://www.postgresql.org/docs/current/catalog-pg-authid.html" rel="noopener noreferrer"&gt;&lt;code&gt;pg_authid&lt;/code&gt;&lt;/a&gt;.&lt;br&gt;
When the &lt;a href="https://www.postgresql.org/docs/current/runtime-config-connection.html#GUC-PASSWORD-ENCRYPTION" rel="noopener noreferrer"&gt;&lt;code&gt;password_encryption&lt;/code&gt; parameter&lt;/a&gt; is set to &lt;code&gt;scram-sha-256&lt;/code&gt;, the value follows this format as described in the &lt;a href="https://www.postgresql.org/docs/current/catalog-pg-authid.html#CATALOG-PG-AUTHID" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; and defined in &lt;a href="https://datatracker.ietf.org/doc/html/rfc5803" rel="noopener noreferrer"&gt;RFC 5803&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SCRAM-SHA-256$&amp;lt;iteration count&amp;gt;:&amp;lt;salt&amp;gt;$&amp;lt;StoredKey&amp;gt;:&amp;lt;ServerKey&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;iteration count&lt;/code&gt;, &lt;code&gt;salt&lt;/code&gt;, &lt;code&gt;StoredKey&lt;/code&gt;, and &lt;code&gt;ServerKey&lt;/code&gt; correspond to the values used in the SCRAM computation flow illustrated below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ackzlajgrtop7vuftbf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ackzlajgrtop7vuftbf.png" alt="Computation flow in SCRAM authentication" width="800" height="230"&gt;&lt;/a&gt;&lt;br&gt;
Diagram: Computation flow in SCRAM authentication&lt;br&gt;
Diagram: SCRAM authentication computation flow&lt;/p&gt;
&lt;h3&gt;
  
  
  Password Registration
&lt;/h3&gt;

&lt;p&gt;Passwords are registered in PostgreSQL using the &lt;code&gt;PASSWORD&lt;/code&gt; clause of the &lt;code&gt;CREATE ROLE&lt;/code&gt; or &lt;code&gt;ALTER ROLE&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;code&gt;CREATE ROLE yourname ... PASSWORD 'yourpassword';&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This sends the plaintext password to the server, where &lt;code&gt;StoredKey&lt;/code&gt; and &lt;code&gt;ServerKey&lt;/code&gt; are computed.&lt;/p&gt;

&lt;p&gt;To avoid sending the plaintext password to the server, you can directly specify the SCRAM format string (as defined in RFC 5803) in the &lt;code&gt;PASSWORD&lt;/code&gt; clause*2.&lt;br&gt;
PostgreSQL tools provide client-side computation methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;PQencryptPasswordConn&lt;/code&gt; function in libpq (&lt;a href="https://www.postgresql.org/docs/current/libpq-misc.html#LIBPQ-PQENCRYPTPASSWORDCONN" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;\password&lt;/code&gt; meta-command in psql (&lt;a href="https://www.postgresql.org/docs/current/app-psql.html#APP-PSQL-META-COMMAND-PASSWORD" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some PostgreSQL variants enforce password policies (e.g., complexity or reuse restrictions) on the server side.&lt;br&gt;
However, if the password is hashed on the client and never sent in plaintext, the server cannot enforce such policies. In such cases, it is the user's responsibility to ensure compliance.&lt;/p&gt;
&lt;h3&gt;
  
  
  Authentication
&lt;/h3&gt;

&lt;p&gt;The actual authentication method is determined by the server.&lt;br&gt;
If the server selects &lt;code&gt;password&lt;/code&gt; authentication, the plaintext password is sent.&lt;br&gt;
To prevent this, the client can set the &lt;a href="https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-REQUIRE-AUTH" rel="noopener noreferrer"&gt;&lt;code&gt;require_auth&lt;/code&gt;&lt;/a&gt; connection parameter explicitly to &lt;code&gt;scram-sha-256&lt;/code&gt; (&lt;a href="https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-REQUIRE-AUTH" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;) to force SCRAM authentication.&lt;/p&gt;
&lt;h2&gt;
  
  
  What is SCRAM Pass-Through Authentication?
&lt;/h2&gt;

&lt;p&gt;SCRAM pass-through authentication takes advantage of the SCRAM property discussed in the &lt;a href=""&gt;previous article&lt;/a&gt;: during a successful authentication session, the server obtains &lt;code&gt;ClientKey&lt;/code&gt;, which is a credential equivalent to the user's password.&lt;/p&gt;

&lt;p&gt;Using &lt;code&gt;ClientKey&lt;/code&gt;, the server can generate &lt;code&gt;ClientSignature&lt;/code&gt; and &lt;code&gt;ClientProof&lt;/code&gt;, and authenticate itself to another server that shares the same credentials—effectively impersonating the user.&lt;/p&gt;
&lt;h3&gt;
  
  
  SCRAM Pass-Through Authentication in PostgreSQL
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://github.com/postgres/postgres/commit/761c79508e7fbc33c1b11754bdde4bd03ce9cbb3" rel="noopener noreferrer"&gt;following commit&lt;/a&gt; was added to the PostgreSQL master branch (REL_18_BETA1):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;postgres_fdw: SCRAM authentication pass-through&lt;br&gt;
This enables SCRAM authentication for postgres_fdw when connecting to&lt;br&gt;
a foreign server without having to store a plain-text password on user&lt;br&gt;
mapping options.&lt;/p&gt;

&lt;p&gt;This is done by saving the SCRAM ClientKey and ServerKey from the&lt;br&gt;
client authentication and using those instead of the plain-text&lt;br&gt;
password for the server-side SCRAM exchange. The new foreign-server&lt;br&gt;
or user-mapping option "use_scram_passthrough" enables this.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;According to the &lt;a href="https://www.postgresql.org/docs/18/postgres-fdw.html#POSTGRES-FDW-OPTION-USE-SCRAM-PASSTHROUGH" rel="noopener noreferrer"&gt;PostgreSQL 18 Beta documentation&lt;/a&gt;, when using &lt;code&gt;postgres_fdw&lt;/code&gt;, if both the source and target servers share the same credentials and the user authenticates to the source with &lt;code&gt;scram-sha-256&lt;/code&gt;, the source server can use the recovered &lt;code&gt;ClientKey&lt;/code&gt; to authenticate to the target. This eliminates the need to store plain-text passwords in the &lt;code&gt;pg_user_mapping&lt;/code&gt; system catalog.&lt;/p&gt;

&lt;p&gt;To support this, a new libpq connection parameter &lt;a href="https://www.postgresql.org/docs/18/libpq-connect.html#LIBPQ-CONNECT-SCRAM-CLIENT-KEY" rel="noopener noreferrer"&gt;&lt;code&gt;scram_client_key&lt;/code&gt;&lt;/a&gt; has been introduced*3.&lt;/p&gt;
&lt;h3&gt;
  
  
  Using SCRAM Pass-Through Authentication in &lt;code&gt;postgresql_fdw&lt;/code&gt;
&lt;/h3&gt;
&lt;h4&gt;
  
  
  Setting Passwords for Remote Server Users
&lt;/h4&gt;

&lt;p&gt;When using SCRAM pass-through authentication, the credentials of the source and destination servers must be exactly the same, including salt and other information. Refer to the credentials of the source server from the &lt;code&gt;pg_authid&lt;/code&gt; system catalog and specify them directly in the &lt;code&gt;PASSWORD&lt;/code&gt; clause as the password for the user on the destination server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="o"&gt;#&lt;/span&gt; &lt;span class="k"&gt;Referencing&lt;/span&gt; &lt;span class="n"&gt;credentials&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="k"&gt;source&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;rolpassword&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_authid&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;rolname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'test'&lt;/span&gt;
                                    &lt;span class="n"&gt;rolpassword&lt;/span&gt;

&lt;span class="c1"&gt;--------------------------------------------------------------------------&lt;/span&gt;
 &lt;span class="n"&gt;SCRAM&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;SHA&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;stored&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;key&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="o"&gt;#&lt;/span&gt; &lt;span class="n"&gt;Change&lt;/span&gt; &lt;span class="n"&gt;credentials&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="k"&gt;connect&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requires&lt;/span&gt; &lt;span class="n"&gt;administrator&lt;/span&gt; &lt;span class="k"&gt;privileges&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="n"&gt;PASSWORD&lt;/span&gt; &lt;span class="s1"&gt;'SCRAM-SHA-256$4096:test-salt$test-stored-key:test-server-key'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Creating User Mappings
&lt;/h3&gt;

&lt;p&gt;When creating a user mapping to an external server, use the &lt;code&gt;use_scram_passthrough&lt;/code&gt; option instead of the &lt;code&gt;password&lt;/code&gt; option.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="o"&gt;#&lt;/span&gt; &lt;span class="n"&gt;PostgreSQL&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;earlier&lt;/span&gt;
 &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="n"&gt;MAPING&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="n"&gt;SERVER&lt;/span&gt; &lt;span class="n"&gt;server2&lt;/span&gt; &lt;span class="k"&gt;OPTIONS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="s1"&gt;'test'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="s1"&gt;'testpassword'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="o"&gt;#&lt;/span&gt; &lt;span class="n"&gt;PostgreSQL&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;
 &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="n"&gt;MAPING&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="n"&gt;SERVER&lt;/span&gt; &lt;span class="n"&gt;server2&lt;/span&gt; &lt;span class="k"&gt;OPTIONS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="s1"&gt;'test'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;use_scram_passthrough&lt;/span&gt; &lt;span class="s1"&gt;'true'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Connect to the source server
&lt;/h4&gt;

&lt;p&gt;Connect to the source PostgreSQL server; since SCRAM pass-through authentication only passes credentials, the source server must be logged in with SCRAM authentication.&lt;/p&gt;

&lt;h3&gt;
  
  
  Storage and Use of &lt;code&gt;ClientKey&lt;/code&gt; in Pass-Through Authentication
&lt;/h3&gt;

&lt;p&gt;In PostgreSQL, SCRAM authentication message exchanges are handled in the &lt;code&gt;scram_exchange&lt;/code&gt; function in &lt;code&gt;src/backend/libpq/auth-scram.c&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;As of May 25, 2025, the master branch unconditionally copies &lt;code&gt;ClientKey&lt;/code&gt; into the global &lt;code&gt;MyProcPort&lt;/code&gt; structure upon successful authentication (&lt;a href="https://github.com/postgres/postgres/blame/master/src/backend/libpq/auth-scram.c#L467" rel="noopener noreferrer"&gt;source&lt;/a&gt;).&lt;br&gt;
This information continues to be maintained during the lifetime of the backend process associated with the connection.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;postgres_fdw&lt;/code&gt;, if the &lt;code&gt;MyProcPort&lt;/code&gt; structure contains the SCRAM keys and &lt;code&gt;use_scram_passthrough&lt;/code&gt; is enabled, those keys are added to the connection parameters (&lt;a href="https://github.com/postgres/postgres/blob/44ce4e1593b1821005b29ffaa19d9cbdd80747b2/contrib/postgres_fdw/connection.c#L571" rel="noopener noreferrer"&gt;source&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The previous article explained how a server can reconstruct &lt;code&gt;ClientKey&lt;/code&gt; during SCRAM authentication, and how this enables transparent login to another server in the same credential-sharing cluster. PostgreSQL’s SCRAM pass-through authentication is a direct application of this property.&lt;/p&gt;

&lt;p&gt;Although this feature must be explicitly enabled, it seems intended not for security reasons, but to give administrators control over the chosen authentication method.&lt;br&gt;
Even in environments where postgres_fdw is not used, &lt;code&gt;ClientKey&lt;/code&gt; is unconditionally retained in memory after a SCRAM login. The &lt;code&gt;MyProcPort&lt;/code&gt; structure where the data is saved is a global variable, and is an area that can be easily referenced if extension modules or user-defined functions to be added to the PostgreSQL server are implemented in C language (or its wrapper).&lt;br&gt;
Thus, DBAs must continue to verify the trustworthiness of any added modules.&lt;/p&gt;

&lt;p&gt;Personally I feel more at ease when I can choose not to keep the &lt;code&gt;ClientKey&lt;/code&gt; in memory.&lt;/p&gt;

&lt;p&gt;Footnotes&lt;/p&gt;

&lt;p&gt;*1: Even if the method is md5, SCRAM authentication will be used if the password is stored in SCRAM format, for compatibility reasons.  &lt;/p&gt;

&lt;p&gt;*2: This means that PostgreSQL does not transparently accept any arbitrary password format.&lt;/p&gt;

&lt;p&gt;*3: A corresponding &lt;code&gt;scram_server_key&lt;/code&gt; parameter is also added for server authentication.&lt;/p&gt;

</description>
      <category>authentication</category>
      <category>postgres</category>
      <category>postgressql</category>
    </item>
    <item>
      <title>Why Does SCRAM Authentication Take This Form?</title>
      <dc:creator>supi sula</dc:creator>
      <pubDate>Mon, 26 May 2025 10:34:14 +0000</pubDate>
      <link>https://dev.to/supi_sula_20573ba512b582d/why-does-scram-authentication-take-this-form-24o9</link>
      <guid>https://dev.to/supi_sula_20573ba512b582d/why-does-scram-authentication-take-this-form-24o9</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;One form of password-based authentication is &lt;a href="https://www.rfc-editor.org/rfc/rfc5802" rel="noopener noreferrer"&gt;SCRAM (Salted Challenge Response Authentication Mechanism)&lt;/a&gt;. SCRAM is a type of challenge-response authentication that uses passwords. It achieves the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The server can verify that the client possesses the same knowledge as when the password was registered.&lt;/li&gt;
&lt;li&gt;The client can verify that the server possesses the knowledge entrusted to it at the time of password registration.&lt;/li&gt;
&lt;li&gt;At no point — during registration or authentication — is there any need to expose the plaintext password outside the client, i.e., the password is never sent to the server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because the server is never told the password, even if the password is reused elsewhere, the server cannot misuse it.&lt;/p&gt;

&lt;p&gt;(This article is a translation of a &lt;a href="https://dev-supisula.hatenablog.com/entry/2025/05/26/070000" rel="noopener noreferrer"&gt;Japanese article&lt;/a&gt; written by the author themselves.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview of SCRAM Authentication
&lt;/h2&gt;

&lt;p&gt;For a detailed description of the SCRAM protocol, refer to &lt;a href="https://www.postgresql.jp/document/17/html/catalog-pg-authid.html" rel="noopener noreferrer"&gt;this article&lt;/a&gt; (in Japanese).&lt;br&gt;
My post focuses not on protocol specifics, but on the information known only to the client or server, and the role of one-way function in the computations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Computation Flow of Key Values in SCRAM
&lt;/h2&gt;

&lt;p&gt;Below is a diagram showing the information involved in SCRAM and the direction of their derivation.&lt;br&gt;
Rectangles represent data; bold arrows represent one-way function; ⊕ denotes XOR. The arrow direction indicates ease of computation.&lt;/p&gt;

&lt;p&gt;Colors indicate data types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Orange: Secret information that must not be disclosed by the client&lt;/li&gt;
&lt;li&gt;Green: Information stored on the server&lt;/li&gt;
&lt;li&gt;Yellow: Per-session information&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ackzlajgrtop7vuftbf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ackzlajgrtop7vuftbf.png" alt="Computation flow in SCRAM authentication" width="800" height="230"&gt;&lt;/a&gt;&lt;br&gt;
Diagram: Computation flow in SCRAM authentication&lt;/p&gt;

&lt;p&gt;(This diagram is a reconstruction based on one found in the previously referenced blog post, customized to reflect my own focus.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Password Registration
&lt;/h3&gt;

&lt;p&gt;When a password is registered, the client derives a &lt;code&gt;SaltedPassword&lt;/code&gt; from the plaintext &lt;code&gt;password&lt;/code&gt; along with a &lt;code&gt;salt&lt;/code&gt; and &lt;code&gt;iteration-count&lt;/code&gt;. Both the &lt;code&gt;password&lt;/code&gt; and &lt;code&gt;SaltedPassword&lt;/code&gt; must remain secret.&lt;/p&gt;

&lt;p&gt;From the &lt;code&gt;SaltedPassword&lt;/code&gt;, two keys — &lt;code&gt;ClientKey&lt;/code&gt; and &lt;code&gt;ServerKey&lt;/code&gt; — are derived, using HMACs with fixed labels "ClientKey" and "ServerKey". Then, &lt;code&gt;ClientKey&lt;/code&gt; is hashed to produce &lt;code&gt;StoredKey&lt;/code&gt;, and both &lt;code&gt;StoredKey&lt;/code&gt; and &lt;code&gt;ServerKey&lt;/code&gt; are stored on the server, along with the &lt;code&gt;salt&lt;/code&gt; and &lt;code&gt;iteration count&lt;/code&gt;. (*1)&lt;/p&gt;

&lt;p&gt;This computation can be done server-side, but may also be performed on the client, which then sends only the results to the server. If all computations are client-side, the server knows only &lt;code&gt;StoredKey&lt;/code&gt; and &lt;code&gt;ServerKey&lt;/code&gt;, and cannot derive &lt;code&gt;ClientKey&lt;/code&gt;, &lt;code&gt;SaltedPassword&lt;/code&gt;, or the original &lt;code&gt;password&lt;/code&gt; due to the one-way nature of the functions involved.&lt;/p&gt;

&lt;p&gt;The “knowledge entrusted to the server” during registration refers to the pair of &lt;code&gt;StoredKey&lt;/code&gt; and &lt;code&gt;ServerKey&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ddzjflfj07fpfxfp8ms.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ddzjflfj07fpfxfp8ms.png" alt="Password registration" width="800" height="230"&gt;&lt;/a&gt;&lt;br&gt;
Diagram: Password registration&lt;/p&gt;

&lt;h3&gt;
  
  
  During Authentication
&lt;/h3&gt;

&lt;p&gt;SCRAM authentication consists of two message exchanges initiated by the client.&lt;/p&gt;

&lt;p&gt;The first exchange constructs &lt;code&gt;AuthMessage&lt;/code&gt;, which functions as a challenge for both client and server. It includes a client-generated and server-generated &lt;code&gt;nonce&lt;/code&gt;, ensuring the message is unique per session.&lt;/p&gt;

&lt;p&gt;The second exchange performs the main authentication, with response computation, transmission, and verification.&lt;/p&gt;

&lt;p&gt;The client sends &lt;code&gt;ClientProof&lt;/code&gt;, asserting it has the same knowledge as during registration.&lt;/p&gt;

&lt;p&gt;The server sends &lt;code&gt;ServerSignature&lt;/code&gt;, asserting it possesses the entrusted knowledge.&lt;/p&gt;

&lt;h4&gt;
  
  
  Creating and Verifying ClientProof
&lt;/h4&gt;

&lt;p&gt;The client, knowing the &lt;code&gt;password&lt;/code&gt;, can compute &lt;code&gt;SaltedPassword&lt;/code&gt;, &lt;code&gt;ClientKey&lt;/code&gt;, and &lt;code&gt;StoredKey&lt;/code&gt; (*2). Using &lt;code&gt;AuthMessage&lt;/code&gt;, it calculates &lt;code&gt;ClientSignature&lt;/code&gt;, then derives &lt;code&gt;ClientProof&lt;/code&gt; via XOR with &lt;code&gt;ClientKey&lt;/code&gt;, and sends this to the server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frt0p0lymif2slioe35dt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frt0p0lymif2slioe35dt.png" alt="Client creating ClientProof" width="800" height="230"&gt;&lt;/a&gt;&lt;br&gt;
Diagram: Client creating &lt;code&gt;ClientProof&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The server, knowing &lt;code&gt;StoredKey&lt;/code&gt; and &lt;code&gt;AuthMessage&lt;/code&gt;, computes &lt;code&gt;ClientSignature&lt;/code&gt;, then XORs it with the received &lt;code&gt;ClientProof&lt;/code&gt; to recover &lt;code&gt;ClientKey&lt;/code&gt;. It re-derives &lt;code&gt;StoredKey&lt;/code&gt; and verifies it against the stored value. &lt;br&gt;
If this matches the stored &lt;code&gt;StoredKey&lt;/code&gt;, the server can conclude that the client in this authentication session was able to compute the correct &lt;code&gt;ClientKey&lt;/code&gt; — that is, the client knows &lt;code&gt;ClientKey&lt;/code&gt;.&lt;br&gt;
Since computing &lt;code&gt;ClientKey&lt;/code&gt; requires the original &lt;code&gt;password&lt;/code&gt;, the server interprets this as evidence that the client still possesses the same knowledge as at the time of password registration.&lt;br&gt;
Of course, this interpretation is based on the assumption that knowing &lt;code&gt;ClientKey&lt;/code&gt; is equivalent to knowing the &lt;code&gt;password&lt;/code&gt;. This is considered an intentional design trade-off in SCRAM.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftk4gb5fuaemu44ohzam8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftk4gb5fuaemu44ohzam8.png" alt="Server verifying ClientProof" width="800" height="230"&gt;&lt;/a&gt;&lt;br&gt;
Diagram: Server verifying &lt;code&gt;ClientProof&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Creating and Verifying ServerSignature
&lt;/h4&gt;

&lt;p&gt;The server computes &lt;code&gt;ServerSignature&lt;/code&gt; from its stored &lt;code&gt;ServerKey&lt;/code&gt; and the session's &lt;code&gt;AuthMessage&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fksjryp9t2aaelpbsdmyb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fksjryp9t2aaelpbsdmyb.png" alt="Server creating ServerSignature" width="800" height="230"&gt;&lt;/a&gt;&lt;br&gt;
Diagram: Server creating &lt;code&gt;ServerSignature&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The client, using its computed &lt;code&gt;ServerKey&lt;/code&gt; and &lt;code&gt;AuthMessage&lt;/code&gt;, calculates &lt;code&gt;ServerSignature&lt;/code&gt; and compares it to the received one. A match confirms that the server knows &lt;code&gt;ServerKey&lt;/code&gt; and is thus authentic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fay0swqi7u9x2ol7a1lju.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fay0swqi7u9x2ol7a1lju.png" alt="Client verifying ServerSignature" width="800" height="230"&gt;&lt;/a&gt;&lt;br&gt;
Diagram: Client verifying &lt;code&gt;ServerSignature&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Implications of the Server Being Able to Derive &lt;code&gt;ClientKey&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Although &lt;code&gt;ClientKey&lt;/code&gt; is not stored on the server, it is reconstructed during successful authentication. Possession of &lt;code&gt;ClientKey&lt;/code&gt; allows the server to compute valid &lt;code&gt;ClientSignature&lt;/code&gt; and &lt;code&gt;ClientProof&lt;/code&gt; for any session — effectively making &lt;code&gt;ClientKey&lt;/code&gt; equivalent to the &lt;code&gt;password&lt;/code&gt; in terms of credential power.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the Server Can Do with &lt;code&gt;ClientKey&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Once the server reconstructs &lt;code&gt;ClientKey&lt;/code&gt;, it can theoretically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Log in to itself as the user&lt;/li&gt;
&lt;li&gt;Log in to a different server (B) as the user, if the same password, salt, and iteration count are used on both servers A and B&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In theory, it also allows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Server A impersonating user X to itself — Since this is a legitimate connection, user X may not be able to deny this connection&lt;/li&gt;
&lt;li&gt;User X denying a connection to server A for the same reason&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Is Server Use of &lt;code&gt;ClientKey&lt;/code&gt; an Abuse?
&lt;/h3&gt;

&lt;p&gt;Although password reuse is a user responsibility, matching &lt;code&gt;salt&lt;/code&gt;s and &lt;code&gt;iteration-count&lt;/code&gt;s are rare (*3). Logging into another server using &lt;code&gt;ClientKey&lt;/code&gt; typically occurs in intentionally credential-sharing environments, like server clusters.&lt;/p&gt;

&lt;p&gt;Thus, such usage may not constitute abuse.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is This a SCRAM Vulnerability?
&lt;/h3&gt;

&lt;p&gt;The fact that &lt;code&gt;ClientKey&lt;/code&gt; is reconstructible is a consequence of its role in authentication. The ability to generate &lt;code&gt;ClientProof&lt;/code&gt; from &lt;code&gt;ClientKey&lt;/code&gt; is an expected feature, not a flaw.&lt;/p&gt;

&lt;h2&gt;
  
  
  Questions and Answers
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why Doesn’t the Server Store &lt;code&gt;ClientKey&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;If the server ends up reconstructing &lt;code&gt;ClientKey&lt;/code&gt;, why not store it instead of &lt;code&gt;StoredKey&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;A: &lt;code&gt;StoredKey&lt;/code&gt; is stored long-term, whereas &lt;code&gt;ClientKey&lt;/code&gt; is only briefly reconstructed. Because &lt;code&gt;ClientKey&lt;/code&gt; is a full credential, storing &lt;code&gt;StoredKey&lt;/code&gt; instead limits exposure risk.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Not Just Send &lt;code&gt;ClientSignature&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;Why does the client need to send &lt;code&gt;ClientProof&lt;/code&gt; instead of just &lt;code&gt;ClientSignature&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;A: Accepting &lt;code&gt;ClientSignature&lt;/code&gt; as proof would mean treating knowledge of &lt;code&gt;StoredKey&lt;/code&gt; as equivalent to client identity. Since &lt;code&gt;StoredKey&lt;/code&gt; is long-term and stored on the server, this weakens security — akin to storing passwords directly. &lt;code&gt;ClientProof&lt;/code&gt;, requiring knowledge of &lt;code&gt;ClientKey&lt;/code&gt;, avoids this issue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Not Use MAC with &lt;code&gt;ClientKey&lt;/code&gt; Instead?
&lt;/h3&gt;

&lt;p&gt;Why not just use &lt;code&gt;ClientKey&lt;/code&gt; to MAC the &lt;code&gt;AuthMessage&lt;/code&gt; directly and treat that as "&lt;code&gt;ClientSignature&lt;/code&gt;"?&lt;/p&gt;

&lt;p&gt;A: In that case, the server couldn’t verify it, since it doesn’t know &lt;code&gt;ClientKey&lt;/code&gt;.&lt;br&gt;
So how can a server that only knows the &lt;code&gt;StoredKey&lt;/code&gt; verify that the client in this authentication session knows the upstream value (&lt;code&gt;ClientKey&lt;/code&gt;) of the one-way function? To solve this problem, the bidirectionality of the XOR operation*4 is used. It is not possible to calculate the inverse function of a one-way function, but the bidirectionality of the XOR operation makes it possible to calculate the upstream value.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Is &lt;code&gt;ServerSignature&lt;/code&gt; Needed?
&lt;/h3&gt;

&lt;p&gt;Why not use &lt;code&gt;ClientSignature&lt;/code&gt; for server authentication too?&lt;/p&gt;

&lt;p&gt;A: &lt;code&gt;ClientSignature&lt;/code&gt; requires &lt;code&gt;StoredKey&lt;/code&gt; or &lt;code&gt;ClientKey&lt;/code&gt;. The client’s &lt;code&gt;ClientProof&lt;/code&gt; alone doesn’t allow the server to recover either. Therefore, the ability to compute &lt;code&gt;ClientSignature&lt;/code&gt; implies possession of &lt;code&gt;StoredKey&lt;/code&gt;. Still, using a distinct &lt;code&gt;ServerKey&lt;/code&gt; and &lt;code&gt;ServerSignature&lt;/code&gt; clarifies the structure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;SCRAM authentication never requires the client to reveal the plaintext password—either during registration or authentication.&lt;/p&gt;

&lt;p&gt;However, it’s important to note that &lt;code&gt;ClientKey&lt;/code&gt;, a secret that fully serves as a credential, can be reconstructed by a legitimate server (that knows &lt;code&gt;StoredKey&lt;/code&gt;) during authentication. This differs from public-key challenge-response schemes.&lt;/p&gt;

&lt;p&gt;On the other hand, a non-legitimate server (without &lt;code&gt;StoredKey&lt;/code&gt;) cannot obtain &lt;code&gt;ClientKey&lt;/code&gt;. This sharply distinguishes SCRAM from hash-based authentication schemes that indiscriminately expose credentials to all servers.&lt;/p&gt;

&lt;p&gt;Footnotes&lt;br&gt;
*1: The SCRAM standard requires both to be stored. If the client remembers them, storage on the server isn’t strictly necessary for mutual authentication.&lt;br&gt;&lt;br&gt;
*2: The salt and iteration count are sent from the server during the first exchange.&lt;br&gt;&lt;br&gt;
*3: This can happen with improper implementations, such as using the user name as a salt. &lt;br&gt;
*4: XOR has the reversible property: X XOR Y = Z implies X XOR Z = Y and Y XOR Z = X.  &lt;/p&gt;

</description>
      <category>authentication</category>
    </item>
  </channel>
</rss>
