Skip to content

Postgres: SCRAM authentication does not SASLprep passwords before computing SaltedPassword #4196

@meng-xu-cs

Description

@meng-xu-cs

I have found these related issues/pull requests

N/A

Description

sqlx-postgres/src/connection/sasl.rs comments the SCRAM step as:

// SaltedPassword := Hi(Normalize(password), salt, i)

but the implementation currently passes options.password directly into hi(...) and hi(...) hashes s.as_bytes() unchanged.

In fact, SQLx already applies saslprep() to the username in the same file, but not to the password.

However, this is not a security issue, as it doesn't allow authentication bypass in any way.

Reproduction steps

1. Use a PostgreSQL server with SCRAM enabled

Create a role whose password contains U+00A0 (the NO-BREAK SPACE char), which SASLprep maps to a normal ASCII space:

DROP ROLE IF EXISTS saslprep_user;
CREATE ROLE saslprep_user LOGIN PASSWORD U&'pass\00A0word';

2. Try two logically equivalent passwords from SQLx

use sqlx::{postgres::PgConnectOptions, ConnectOptions};

#[tokio::main]
async fn main() {
    let raw_password = "pass\u{00A0}word"; // U+00A0 NO-BREAK SPACE
    let normalized_password = "pass word"; // SASLprep result

    let base = PgConnectOptions::new()
        .host("127.0.0.1")
        .port(5432)
        .database("postgres")
        .username("saslprep_user");

    let raw = base.clone().password(raw_password);
    let normalized = base.clone().password(normalized_password);

    println!("raw: {:?}", raw.connect().await);
    println!("normalized: {:?}", normalized.connect().await);
}

Expected behavior

Both connection attempts should succeed.

Per SCRAM, the client-side proof should be computed from Hi(Normalize(password), salt, i), so both "pass\u{00A0}word" and "pass word" should produce the same SCRAM proof.

Actual behavior

The raw password form fails with 28P01 / password authentication failed, while the normalized form succeeds.

This happens because SQLx computes the SCRAM proof from the raw Rust UTF-8 bytes of the password instead of the SASLprep-normalized password.

SQLx version

main branch

Enabled SQLx features

default

Database server and version

Postgres

Operating system

MacOS

Rust version

1.94.0 (4a4ef493e 2026-03-02)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions