But let's start from the beginning. You may however start to play with them!
You are using Cyrus SASL for authentification purposes? Supposedly with the Cyrus IMAP server or Postfix (the two major applications using Cyrus SASL)? Then you'll know the problem: You may use saslauthd(8) to connect to an authentication database as Unix PAM or LDAP. Problem: You will only be able to use plaintext logins. Why?
If using saslauthd, the SASL-enabled server gets the password given by the user/client, and asks saslauthd whether it is correct or not.
Plaintext authentication can be sufficient when using it with SSL/TLS. In IMAP environments this may be a good solution if all mail clients can be configured in that way. With SMTP you will have a problem. Many Mailservers being able to initiate authenticated outgoing SMTP connections do not use SSL/TLS. Many of them want to use CRAM-MD5 as it is defined in many standard drafts.
The Cyrus SASL v2 library does rely on knowing the "userPassword" as plaintext attribute to be able to compute the needed numbers for CRAM-MD5 and DIGEST-MD5 authentication. To get this cleartext password, it uses an "auxprop". Auxprops included in the standard Cyrus SASL 2 distribution are sasldb (the local database file store), MySQL (to retrieve values from a MySQL database) and ldapdb (to access SASL-enabled LDAP servers via proxy auth). In case you want to access an LDAP resource without using SASL itself but with a simple bind (over TLS/SSL), then my ldap auxprop is for you.
The problem? You will need a database carrying all the user passwords as cleartext entries. A security nightmare. Unless you are not using different passwords for different services you will have a big problem when this database gets known by a hacker (or the british government, you'll find these files in a suburban train then or on a lost USB stick).
The SASL v1 library was a little bit more intelligent in this case. To set a password, each mechanism did set its own secret in the database. These attributes were named "cmusaslsecretMECHANISM", so for CRAM-MD5 it was "cmusaslsecretCRAM-MD5", for cleartext logins it was "cmusaslsecretPLAIN".
The "trick": These attributes are still used by the "new" SASL v2 library. SASLv2 looks for "userPassword" and for the appropriate "cmusaslsecret"-value. But they can't be set any more, as they are marked as "obsolete". When calling "saslpasswd2" it even removes these attributes from the sasldb.
To use these attributes, you'll have to do two things:
First, extend your SQL table or your LDAP schema with three columns/attributes:
In SQL it has to be a 8-bit-binary type (BLOB) or a character field (depending on your sql auxprop implementation). For LDAP, just use "SYNTAX 22.214.171.124.4.1.14126.96.36.199.40" (octet String).
In the following example, the period (".") is used as concatenation operation: So
"pascal" . "gienger"
would result in
1. cmusaslsecretPLAIN - plaintext authentication
To use plain/login without saslauthd but you want to use your auxprop to retrieve data, you must set cmusaslsecretPLAIN in your database.
The algorithm is quite simple:
cmusaslsecretPLAIN := salt . \0 . md5(salt . 'sasldb' . password)
salt := 16 random bytes
password := user password
First, create 16 random bytes as "salt". Take the MD5 value of the string consisting of these 16 bytes, concatenated by the string "sasldb" and ended by the user's password.
You will get 33 bytes, including the NULL byte in the middle.
This was easy. And very effective. An attacker stealing these values has got rubbish. He can't even do a useful thing with it. He has no possibility to reconstruct the password apart from trying dictionary attacks. (using the first 16 bytes as salt value and trying many passwords).
2. cmusaslsecretDIGEST-MD5 - DIGEST authentication
This is easy too. However, DIGEST-MD5 is not so much widespread as CRAM-MD is. It offers higher security though, because it can guarantee message integrity.
The cmusaslsecretDIGEST-MD5-Attribute has to be calculated like this:
cmusaslsecretDIGEST-MD5 := md5(username . ':' . realm . ":" . password)
This value will be 16 bytes in size (md5 result size).
The reason for that to work?
In DIGEST-MD5, the server (and the client) has to calculate
A1 := md5(username . ':' . realm . ":" . password) . ':' . nonce . ':' cnonce
A1 := cmusaslsecretDIGEST-MD5 . ':' . nonce . ':' cnonce
nonce is a server-specified octet string created by random.
cnonce is a client-specified octet string created by random.
This is only one step, the next step is to combine this with A2, which defines the type of action/authentication wanted.
The intelligent thing here is the realm. This is very important, so you may use another realm for every service - thus resuting in different secrets "cmusaslsecretDIGEST-MD5". Why that?
If an attacker gets your cmusaslsecretDIGEST-MD5-Values, he can login with this in your server using the realm specified. He can't use these md5 values to login to other servers using different realms, because he can't calculate the new md5 value resulting from the new realm.
It your database is compromised, you will have to change the realm of your service and recalculate all cmusaslsecretDIGEST-MD5-values to repair the situation.
3. cmusaslsecretCRAM-MD5 - CRAM mechanism
Here we have the example of an easy mechanism (from the client's view) and a complicated mechanism (in terms of storing a secret not being identical to the password).
CRAM-MD5 relies on HMAC-MD5. As you know (or you'll read here) HMAC consists of two hashes, an inner and an outer one. HMAC uses a key to process messages. At the end you have a HMAC hash. HMAC-MD5 means you take MD5 for the inner and outer hash. It is named "keyed" because you will not only use a message (like in md5) but you also specify a key.
Next, you have to know that MD5 is a state machine. It has to be initialized, then data has to be pushed to it, and after a finalization stage, you get the md5 hash value for your data. It is not important however whether you push all the data in one step or you put it byte by byte. The md5 machine maintains an internal state, defined by 4 32bit-integers, giving 16 bytes of state value.
CRAM-MD5 uses the user's password as HMAC's key. Basically, the server sends a random string to the client, and the client responds with the username and the HMAC-MD5 output of the server's random string (using the password as HMAC key).
To avoid storing the cleartext password on the server side, we can do a trick: Just initialize the HMAC-MD5 algorithm and assign the key - without sending one byte of message. The 8 state integer variables (4 for the inner md5 state machine, 4 for the outer md5 state machine) are a function of the password then.
You won't be able to calculate the cleartext password from these 32 bytes, but you may compare (identical state values represent identical keys = passwords) and you still can do dictionary attacks.
If an attacker gets your cmusaslsecretCRAM-MD5 values, it can login to your server but also on all other servers using CRAM-MD5. Just use these values to initialize the HMAC-MD5-function and then pass the server random string and "bingo!" you're in (if you know the user name).
So restrict only one service of your site to CRAM-MD5 when using the same password for many things (normally you do that with Postfix Mail and Cyrus IMAP, so an attacker can use these values to read mail and send spam, but not to login to legal services or other things) or use different passwords for every service you offer with CRAM-MD5.
In C you would write (using RSA's public available MD5 header- and source files):
hmac_md5_init(&hmac, password, passwordlength);
state.istate[i] = htonl(hmac.ictx.state[i]);
state.ostate[i] = htonl(hmac.octx.state[i]);
Your "buffer" has your needed 32 state bytes then.
Use these as cmusaslsecretCRAM-MD5.
htonl() is important to be independent of machine's endianness.
To play, you may use my example cmusaslsecret-calculation page to get the idea. Look how cmusaslsecretPLAIN changes on every submit, cmusaslsecretCRAM-MD5 changes only with the password and cmusaslsecretDIGEST-MD5 changes with all three values.