SASL Authentication and SCRAM
Introduction
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 (XMPP only), 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, libmongoc, MailKit/MimeKit, Mellium SASL, Memcached, MongoDB, mpop, msmtp, MySQL 8.0.23+, NeoMutt, ogrebgr/scram-sasl, ongres/scram, OpenDJ, passlib.hash.scram, Pgpool-II/pgpoolAdmin 4.0.0, PhysoTronic/SASL-SCRAM-SHA256, PostgreSQL 10+, puppetlabs-postgresql, pwithnall/libscram, PyMongo 3.7, Rust SASL, Rust SCRAM, SnappyMail, supercaracal/scram-sha-256, 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, YugabyteDB 2.5
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, processone/xmpp, python-nbxmpp, 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:
- ba0f3/scram.nim, 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
SCRAM-SHA-1(-PLUS)
Overview
The basic overview of how this mechanism works is:
- The client sends the username it wants to authenticate as.
- 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).
- The client hashes the password with the given salt for the given number of iterations.
- The client sends the result back.
- 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
normalizedPassword
. 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
clientNonce
. - The initialMessage is:
"n=" .. username .. ",r=" .. clientNonce
- The client prepends the GS2 header (
"n,,"
) to the initialMessage and base64-encodes the result. It sends this as its first message:<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="SCRAM-SHA-1"> biwsbj1yb21lbyxyPTZkNDQyYjVkOWU1MWE3NDBmMzY5ZTNkY2VjZjMxNzhl </auth>
- The server responds with a challenge. The data of the challenge is base64 encoded:
<challenge xmlns="urn:ietf:params:xml:ns:xmpp-sasl"> cj02ZDQ0MmI1ZDllNTFhNzQwZjM2OWUzZGNlY2YzMTc4ZWMxMmIzOTg1YmJkNGE4ZTZmODE0YjQyMmFiNzY2NTczLHM9UVNYQ1IrUTZzZWs4YmY5MixpPTQwOTY= </challenge>
- The client base64 decodes it:
r=6d442b5d9e51a740f369e3dcecf3178ec12b3985bbd4a8e6f814b422ab766573,s=QSXCR+Q6sek8bf92,i=4096
- The client parses this:
- r = This is the serverNonce. The client MUST ensure that it starts with the clientNonce it sent in its initial message.
- s = This is the salt, base64 encoded (yes, this is base64-encoded twice!)
- i = This is the number of iterations, i.
- 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)
- The client base64 encodes the
clientFinalMessage
and sends it as a response:<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl"> Yz1iaXdzLHI9NmQ0NDJiNWQ5ZTUxYTc0MGYzNjllM2RjZWNmMzE3OGVjMTJiMzk4NWJiZDRhOGU2ZjgxNGI0MjJhYjc2NjU3MyxwPXlxbTcyWWxmc2hFTmpQUjFYeGFucG5IUVA4bz0= </response>
-
If everything went well, you'll get a
<success>
response from the server:<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'> dj1wTk5ERlZFUXh1WHhDb1NFaVc4R0VaKzFSU289 </success>
Base64 decoded this contains:
v=pNNDFVEQxuXxCoSEiW8GEZ+1RSo=
- The client MUST make sure the value of v is the base64 encoding of the
serverSignature
(yes, this value is also base64-encoded twice).
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
clientKey
, 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
initialMessage
part of theauthMessage
does not include the GS2 header (in most situations, this is"n,,"
).
Test vectors
Here is a complete example:
Username: user
Password: pencil
Client generates the random nonce fyko+d2lbbFgONRv9qkxdawL
Initial message: n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL
Server generates the random nonce 3rfcNHYJY1ZVvWVs7j
Server replies: r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096
The salt (hex): 4125c247e43ab1e93c6dff76
Client final message bare: c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j
Salted password (hex): 1d96ee3a529b5a5f9e47c01f229a2cb8a6e15f7d
Client key (hex): e234c47bf6c36696dd6d852b99aaa2ba26555728
Stored key (hex): e9d94660c39d65c38fbad91c358f14da0eef2bd6
Auth message: n=user,r=fyko+d2lbbFgONRv9qkxdawL,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096,c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j
Client signature (hex): 5d7138c486b0bfabdf49e3e2da8bd6e5c79db613
Client proof (hex): bf45fcbf7073d93d022466c94321745fe1c8e13b
Server key (hex): 0fe09258b3ac852ba502cc62ba903eaacdbf7d31
Server signature (hex): ae617da6a57c4bbb2e0286568dae1d251905b0a4
Client final message: c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=
Server final message: v=rmF9pqV8S7suAoZWja4dJRkFsKQ=
Server's server signature (hex): ae617da6a57c4bbb2e0286568dae1d251905b0a4
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