SASL Authentication and SCRAM

State of Play
Go here: https://github.com/scram-xmpp/info/issues/1

SCRAM-SHA-1(-PLUS)
Salted Challenge Response Authentication Mechanism (SCRAM) SASL and GSS-API Mechanisms (SCRAM-SHA-1(-PLUS) is a SASL mechanism improving on DIGEST-MD5.


 * RFC6331: Moving DIGEST-MD5 to Historic
 * CRAM-MD5 to Historic (draft-ietf-sasl-crammd5-to-historic-00 + draft-zeilenga-luis140219-crammd5-to-historic-00)

Its main benefits are in offering both a method to salt and hash the password in storage and in transit. This page aims to give a short introduction on how to implement it in a client.

With changes from TLS 1.2 to TLS 1.3, an Internet-Draft is in progress for TLS Binding and TLS 1.3: Channel Bindings for TLS 1.3: draft-ietf-kitten-tls-channel-bindings-for-tls13.

SCRAM-SHA-256(-PLUS)
Please note that there is now RFC7677: SCRAM-SHA-256 and SCRAM-SHA-256-PLUS Simple Authentication and Security Layer (SASL) Mechanisms (since November 2015).

Already integrated by several XMPP softwares:
 * Servers: DJabberd 0.90+, Erlang Solutions MongooseIM 3.7+, Isode M-Link, Jackal IM, Metronome IM, ProcessOne ejabberd 20.12+, Prosody IM 0.12.x, Tigase XMPP Server 8.0+
 * Clients: Conversations, CoyIM, eyeCU, Gajim 1.2.0+, KDE Kaidan, Miranda NG, Mozilla Thunderbird 71+, Psi/Psi+ (with QCA), Tigase Beagle IM, Tigase Siskin IM, Tigase Stork IM, UWPX, Vacuum IM
 * Libraries: cr-xmpp, libstrophe, liangdefeng/Sharp.Xmpp.Client, Mellium XMPP, processone/xmpp, python-nbxmpp, QXmpp, Tigase JaXMPP, TigaseSwift, Stanza, Wocky, xmpp-rs

Others:
 * aiokafka, aiosasl, Atheme, Auth_SASL/Auth_SASL2, Authen-SCRAM, cassandra-secure-plugin, ba0f3/scram.nim, Couchbase, Cyrus SASL, DataEnter CryptoFilter, DataEnter POPBeamer, DataEnter SMTPBeamer, DataEnter XWall, Dovecot, Erlang Solutions Escalus, Exim (with gsasl), fast_scram, GNU SASL (gsasl) 1.9.1+, Haystack, Kafka, ldaptive, MailKit/MimeKit, Mellium SASL, Memcached, MongoDB, mpop, msmtp, MySQL 8.0.23+, NeoMutt, ogrebgr/scram-sasl, ongres/scram, OpenDJ, passlib.hash.scram, PhysoTronic/SASL-SCRAM-SHA256, PostgreSQL 10+, pwithnall/libscram, PyMongo 3.7, Rust SASL, Rust SCRAM, Skyspark, SquirelMail, tlocke/scramp, Tigase TTS-NG, trondn/java-sasl-scram-sha1, UnboundID LDAP SDK, Vert.x SCRAM, WildFly Elytron, xdg-go/scram, xmpp-webhook

SCRAM-SHA-512(-PLUS)
Possibly, also adding SCRAM-SHA-512 and SCRAM-SHA-512-PLUS Simple Authentication and Security Layer (SASL) Mechanisms: draft-melnikov-scram-sha-512

Already integrated by several XMPP softwares:
 * Servers: DJabberd 0.90+, Erlang Solutions MongooseIM 3.7+, Isode M-Link, Jackal IM, Metronome IM, ProcessOne ejabberd 20.12+, Tigase XMPP Server 8.0+
 * Clients: Conversations, CoyIM, eyeCU, KDE Kaidan, Miranda NG, Psi/Psi+ (with QCA), Tigase Stork IM, Vacuum IM
 * Libraries: cr-xmpp, liangdefeng/Sharp.Xmpp.Client, libstrophe, QXmpp, Tigase JaXMPP, Wocky, processone/xmpp

Others:
 * aiokafka, Atheme, Auth_SASL/Auth_SASL2, Authen-SCRAM, ba0f3/scram.nim, Couchbase, Cyrus SASL, DataEnter CryptoFilter, DataEnter POPBeamer, DataEnter SMTPBeamer, DataEnter XWall, Dovecot, Erlang Solutions Escalus, fast_scram, Haystack, Kafka, ldaptive, MailKit/MimeKit, Memcached, NeoMutt, ogrebgr/scram-sasl, OpenDJ, passlib.hash.scram, pwithnall/libscram, Skyspark, tlocke/scramp, Tigase TTS-NG, trondn/java-sasl-scram-sha1, UnboundID LDAP SDK, WildFly Elytron

SCRAM-SHA3-512(-PLUS)
Possibly, also adding SCRAM-SHA3-512 and SCRAM-SHA3-512-PLUS Simple Authentication and Security Layer (SASL) Mechanisms: draft-melnikov-scram-sha3-512

Already integrated by several XMPP softwares:
 * Servers: Jackal IM
 * Clients: KDE Kaidan
 * Libraries: QXmpp

Others:
 * tlocke/scramp

Order
Warning: In RFC8600: Using Extensible Messaging and Presence Protocol (XMPP) for Security Information Exchange (June 2019):

"When using the SASL SCRAM mechanism, the SCRAM-SHA-256-PLUS variant SHOULD be preferred over the SCRAM-SHA-256 variant, and SHA-256 variants [RFC7677] SHOULD be preferred over SHA-1 variants [RFC5802]".

But it has been changed in draft-ietf-kitten-password-storage-02 (2020)
 * SCRAM-SHA-256-PLUS
 * SCRAM-SHA-1-PLUS
 * SCRAM-SHA-256
 * SCRAM-SHA-1

Overview
The basic overview of how this mechanism works is:


 * 1) The client sends the username it wants to authenticate as.
 * 2) The server sends back the salt for that user and the number of iterations (either by generating them or looking them up in its database for the given username).
 * 3) The client hashes the password with the given salt for the given number of iterations.
 * 4) The client sends the result back.
 * 5) The server does a variation of the hashing and sends it result back to the client, so the client can also verify that the server had the password/a hash of the password.

The cryptographic algorithms needed are SHA-1, HMAC with SHA-1 and PBKDF2 with SHA-1. It is advised to find libraries to use these algorithms instead of implementing them from scratch.

In detail
 First normalize the password (using SASLprep), this will be. This is to ensure the UTF8 encoding can't contain variations of the same password. Pick a random string (for example 32 hex encoded bytes). This will be . The initialMessage is:

"n=" .. username .. ",r=" .. clientNonce

 The client prepends the GS2 header to the initialMessage and base64-encodes the result. It sends this as its first message:

 biwsbj1yb21lbyxyPTZkNDQyYjVkOWU1MWE3NDBmMzY5ZTNkY2VjZjMxNzhl



The server responds with a challenge. The data of the challenge is base64 encoded:

 cj02ZDQ0MmI1ZDllNTFhNzQwZjM2OWUzZGNlY2YzMTc4ZWMxMmIzOTg1YmJkNGE4ZTZmODE0YjQyMmFiNzY2NTczLHM9UVNYQ1IrUTZzZWs4YmY5MixpPTQwOTY= 

The client base64 decodes it:

r=6d442b5d9e51a740f369e3dcecf3178ec12b3985bbd4a8e6f814b422ab766573,s=QSXCR+Q6sek8bf92,i=4096 

The client parses this: <ul> r = This is the serverNonce. The client MUST ensure that it starts with the clientNonce it sent in its initial message.</li> s = This is the salt, base64 encoded (yes, this is base64-encoded twice!)</li> i = This is the number of iterations, i.</li> </ul>

</li> The client computes:

clientFinalMessageBare = "c=biws,r=" .. serverNonce saltedPassword = PBKDF2-SHA-1(normalizedPassword, salt, i) clientKey = HMAC-SHA-1(saltedPassword, "Client Key") storedKey = SHA-1(clientKey) authMessage = initialMessage .. "," .. serverFirstMessage .. "," .. clientFinalMessageBare clientSignature = HMAC-SHA-1(storedKey, authMessage) clientProof = clientKey XOR clientSignature serverKey = HMAC-SHA-1(saltedPassword, "Server Key") serverSignature = HMAC-SHA-1(serverKey, authMessage) clientFinalMessage = clientFinalMessageBare .. ",p=" .. base64(clientProof) </li>

The client base64 encodes the  and sends it as a response:

<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl"> Yz1iaXdzLHI9NmQ0NDJiNWQ5ZTUxYTc0MGYzNjllM2RjZWNmMzE3OGVjMTJiMzk4NWJiZDRhOGU2ZjgxNGI0MjJhYjc2NjU3MyxwPXlxbTcyWWxmc2hFTmpQUjFYeGFucG5IUVA4bz0= </li>

 If everything went well, you'll get a  response from the server:

<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'> dj1wTk5ERlZFUXh1WHhDb1NFaVc4R0VaKzFSU289

Base64 decoded this contains:

v=pNNDFVEQxuXxCoSEiW8GEZ+1RSo=

</li> The client MUST make sure the value of v is the base64 encoding of the  (yes, this value is also base64-encoded twice).</li> </ol>

Extras
This is the basic version of the algorithm. You can extend it to do:


 * Channel binding. This mixes in some information from the TLS connection to the procedure to prevent MitM attacks.
 * Hashed storage. If the server always sends the same salt and i values, then the client can store only, instead of the user's password. This is more secure (as the client doesn't need to store the password, just a hard to reverse salt) and faster, as the client doesn't need to do all the hashing every time.

Common pitfalls

 * Don't assume anything about the length of the nonces or salt (though if you generate them, make sure they are long enough and cryptographically random).
 * The salt is base64 encoded and can contain any data (embedded NULs).
 * Not using SASLprep may work fine for people using ASCII passwords, but it may completely break logging in for people using other scripts.
 * The  part of the   does not include the GS2 header (in most situations, this is  ).

Test vectors
Here is a complete example:

Username:

Password:

Client generates the random nonce

Initial message:

Server generates the random nonce

Server replies:

The salt (hex):

Client final message bare:

Salted password (hex):

Client key (hex):

Stored key (hex):

Auth message:

Client signature (hex):

Client proof (hex):

Server key (hex):

Server signature (hex):

Client final message:

Server final message:

Server's server signature (hex):

SCRAM-SHA-256(-PLUS)
Possibly, also adding RFC7677: SCRAM-SHA-256 and SCRAM-SHA-256-PLUS Simple Authentication and Security Layer (SASL) Mechanisms

SCRAM-SHA-512(-PLUS)
Possibly, also adding SCRAM-SHA-512 and SCRAM-SHA-512-PLUS Simple Authentication and Security Layer (SASL) Mechanisms: draft-melnikov-scram-sha-512

SCRAM-SHA3-512(-PLUS)
Possibly, also adding SCRAM-SHA3-512 and SCRAM-SHA3-512-PLUS Simple Authentication and Security Layer (SASL) Mechanisms: draft-melnikov-scram-sha3-512

Channel Bindings

 * RFC5056: On the Use of Channel Bindings to Secure Channels
 * RFC5929: Channel Bindings for TLS
 * Channel-Binding Types
 * Channel Bindings for TLS 1.3: draft-ietf-kitten-tls-channel-bindings-for-tls13

IANA

 * Simple Authentication and Security Layer (SASL) Mechanisms

LDAP

 * RFC5803: Lightweight Directory Access Protocol (LDAP) Schema for Storing Salted: Challenge Response Authentication Mechanism (SCRAM) Secrets

HTTP

 * RFC7804: Salted Challenge Response HTTP Authentication Mechanism

2FA

 * Extensions to Salted Challenge Response (SCRAM) for 2 factor authentication: draft-melnikov-scram-2fa