XMPP SASL SCRAM-SHA1 Authentication
Asked Answered
E

1

14

Recently, I was able to get MD5 authentication working for XMPP streams in Swift IOS following the instructions on the following two websites (I used the CC-MD5 function of Apple's CommonCrypto C library for the actual hashing):

http://wiki.xmpp.org/web/SASLandDIGEST-MD5

http://www.deusty.com/2007/09/example-please.html

I'm searching for a similar explanation for how to get other hashed SASL authentication schemes working, especially SCRAM-SHA1. I have found the official RFC5802 document but I'm having a lot of trouble understanding it (it is not specific to XMPP either). I would appreciate a simpler explanation or some simple readable code (C, PHP, C++, Javascript, Java) specific to XMPP authentication that doesn't use libraries for anything other than the actual hashing.

I'm interested in understanding the process and am not looking to use the ios XMPP-Framework. Any help would be appreciated.

Enthrall answered 27/3, 2015 at 10:21 Comment(1)
Step 1. Search for an open source XMPP library/implementation that implements SCRAM. Step 2. Look at the code. You free to use the open source code or create derivate work as long as you obey the terms of the license of the code.Brindled
B
75

SCRAM-SHA-1

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 you'll need are SHA-1, HMAC with SHA-1 and PBKDF2 with SHA-1. You should look up how to use them in your language/framework, as I don't recommend implementing them from scratch.

In detail

  1. 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.

  2. Pick a random string (for example 32 hex encoded bytes). This will be clientNonce.

  3. The initialMessage is "n=" .. username .. ",r=" .. clientNonce (I'm using .. for string concatenation).

  4. 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>
    
  5. 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>
    
  6. The client base64 decodes it:

     r=6d442b5d9e51a740f369e3dcecf3178ec12b3985bbd4a8e6f814b422ab766573,s=QSXCR+Q6sek8bf92,i=4096
    
  7. 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.
  8. 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)
    
  9. The client base64 encodes the clientFinalMessage and sends it as a response:

     <response xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
         Yz1iaXdzLHI9NmQ0NDJiNWQ5ZTUxYTc0MGYzNjllM2RjZWNmMzE3OGVjMTJiMzk4NWJiZDRhOGU2ZjgxNGI0MjJhYjc2NjU3MyxwPXlxbTcyWWxmc2hFTmpQUjFYeGFucG5IUVA4bz0=
     </response>
    
  10. If everything went well, you'll get a <success> response from the server:

      <success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
          dj1wTk5ERlZFUXh1WHhDb1NFaVc4R0VaKzFSU289
      </success>
    
  11. Base64 decoded this contains:

      v=pNNDFVEQxuXxCoSEiW8GEZ+1RSo=
    
  12. The client MUST make sure the value of v is the base64 encoding of the serverSignature.

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 saltedPassword 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 salted hash) and faster, as the client doesn't need to do all the key stretching every time.

    The server can also use hashed storage: the server can store only salt, i, storedKey and serverKey. More info on that here.

  • Possibly, also adding SCRAM-SHA-256 (though server support seems non-existent).

Pitfalls

Some 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 the authMessage does not include the GS2 header (in most situations, this is "n,,").

Test vectors

If you want to test your implementation, here are all the intermediate results for the example from the RFC:

  • 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

Buttocks answered 27/3, 2015 at 11:45 Comment(6)
do you mind if I give you a hug/kiss/fistbump for posting the values of all the intermediate things? You're the best person!Alic
I asked this question just over a year ago, and thinking back to how much this helped me, I just want to say that it is answers like this that make stack overflow great.. and you for taking the time to answer like this!Enthrall
This is pure treasure. If I want to learn as much from the RFC-s and other DOC-s I would need hours/days of reading time. Thank you for sacrificing your time (writing this) to save out time!Expectoration
I'm sorry but, using your algorithm, it is not clear what "Client Key" stands for. I tried on the test data you gave, the literal string "Client Key" seems not to be right, as well as the client nonce... Starting from the right salted password, both HMAC give different results from your clientKey. Can you elaborate on this "Client Key" ?Tangent
@Tangent It is literally "Client Key". I just tried it again with the test vectors and it matches. Maybe you're accidentially hex encoding the salted password? Or maybe swapping the key and value for HMAC?Buttocks
Indeed it worked in the end, I inverted "key" and "message" in the HMAC. Sorry for the trouble ! Your post helped me a lot, thank you very much !Tangent

© 2022 - 2024 — McMap. All rights reserved.