(At least, AFAIK!)

Recently, I have been contributing to another project outside of KDE: the Prosody XMPP server. Prosody is fairly new (the project began in 2008) and is written in Lua. I’ve been using it for a long time now, and it’s a really nice, rock-solid server. The code is also pretty hackable, being both modular and fairly clean.

My impetus for working on it was that at work we have been using the OpenFire XMPP server. It’s a big Java beast and is all but unmaintained at this point. Since I’ve had good experiences with Prosody and it already supported most of the functionality I needed, I decided to see how it handled a migration at work.

One thing it *didn’t* support was pluggable authentication. The basics for this were in existence, but the code was not yet right. After some pestering, Matthew Wild and Waqas Hussain, the two main Prosody developers, fleshed out the API necessary, and I began to work on separating out the built-in authentication code into a plugin that could be swapped out for other providers.

Once this was working, my next step was to attack something that has been bugging me on various XMPP servers to this point: plaintext password storage. To the best of my knowledge, every other XMPP server stores your credentials in plaintext or via easily reversible encryption with the key stored with your user account — in essence, simple obfuscation. The reason is because of the XMPP standards’ mandate that SASL PLAIN and DIGEST-MD5 be supported authentication methods. While SASL PLAIN transfers the password in cleartext (over the encrypted tunnel between client and server), thus lending itself well to comparison against a hash, DIGEST-MD5 does not. It consists of multiple possible authentication schemes based on the abilities of the client/server, and with different tranferred credentials depending on which scheme. It would require at least four saved tokens for every user, to be used depending on the mechanism the client and server negotiated, and updating these when passwords changed was not necessarily trivial.

So, I decided to make a tradeoff — you can store a plaintext password on the server and have DIGEST-MD5 available, or you can be limited to PLAIN and have a plaintext password go through the encrypted tunnel between client and server but have it hashed in the server’s storage. Personally, since I trust the server I’m connecting to, I’d rather have the latter. ejabberd makes a claim here that it’s more secure to have passwords sent in hashed form over the network and stored in plaintext on the server, but I think this claim is not well-reasoned. Like in many cases, security in this instance is relative. If you’re using SSL/TLS communications, which is by far the norm between XMPP clients and servers these days (in fact, in most clients you must explicitly enable plaintext authentication over unencrypted streams), the passwords are already encrypted when sent over the network — so unless you fear a MITM attack, you’re not going to be vulnerable to snooping. But you *will* be vulnerable to someone hacking onto your server and simply reading your plaintext passwords from a file, and that’s bad. I think that ejabberd’s claim is out-of-date now that encrypted communications between XMPP clients and servers are the norm, and you’ll be better off in most cases with having the one place the password is permanently stored be in a hashed form. (As you’ll soon see, you’re about to be able to have your cake and eat it too, anyways.)

The next step was deciding which hash algorithm to use. There’s something called SCRAM, which stands for Salted Challenge Response, and is an upcoming SASL authentication paradigm. It uses an open standard hash function called PBKDF2, which basically works like this: take the password, salt it with a large random series of bits, and then run it through the SHA-1 function. 4096 times. Rainbow table *that*. If you like, you can swap out which hash function you use, so you could use SHA-256 or some other function, and you can do it more than 4096 times if you like, too.

By limiting what information is passed between server and client, using secure hashes like PBKDF2, and never transmitting the password in cleartext to the server, SCRAM provides a SASL mechanism that is both highly secure and more standardized than DIGEST-MD5.

Now, back to the hashed password backend. Support for SCRAM is beginning to appear in some XMPP clients, and support was mostly coded into Prosody as well, so I asked the guy who wrote it, the excellent Tobias Markmann, to expose some bits of the SCRAM capabilities such that I could use the PBKDF2 functionality to store password hashes, as well as allowing arbitrary salts and iteration counts to be defined. He did this, and I coded up the backend, and suddenly Prosody became the first XMPP server, to my knowledge, that can securely store your password server-side.

The backend provides seamless upgrades from the plaintext backend; it will simply take the password that currently exists for the user, generate a random salt, and create the hashed credentials, removing the plaintext password.

Tobias is currently taking it a step further: he’s modifying the backend to store bits of information required by the SCRAM algorithm instead of the direct password hash. What this means is that if you end up using a client that only supports PLAIN authentication, those bits can be generated by your password on the fly and used to verify against. However, if your client supports SCRAM authentication, the backend will now be able to serve up the bits necessary for SCRAM-SASL. So you’ll have secure server storage *and* a secure exchange from client to server (in addition to the normal SSL/TLS tunnel). Tobias is, I believe, working as a SoC student on Psi — so I’m sure it’ll be working between Psi and Prosody pretty soon, if he doesn’t have it done already.

The hashed password backend isn’t currently the default, but if you’re running Prosody you can enable it by adding auth_internal_hashed to your modules and defining (in your host) the line

authentication = "internal_hashed";

Once it’s tested more thoroughly, and especially when SCRAM support is finished and working in more clients, expect it to become the default backend.