I was playing with a few SMTP authentication mechanisms while setting up my SMTP server. I found CRAM-MD5 authentication mechanism particularly interesting as it is a challenge-response authentication mechanism and involves HMAC-MD5. Let me first show a complete session with an SMTP server before coming to CRAM-MD5. Note that I am not using TLS or SSL in the sessions displayed below so that the SMTP traffic can be seen as plaintext. In practice, any email program should be configured to use TLS or SSL while having a session with an SMTP server. In the following session, I have selected the PLAIN authentication mechanism.

susam@nifty:~$ telnet susam.in 25
Trying 106.187.41.241...
Connected to susam.in.
Escape character is '^]'.
220 tesseract.susam.in ESMTP Exim 4.72 Mon, 07 Nov 2011 20:27:56 +0530
EHLO nifty.localdomain
250-tesseract.susam.in Hello nifty.localdomain [122.167.80.194]
250-SIZE 52428800
250-PIPELINING
250-AUTH PLAIN LOGIN CRAM-MD5
250-STARTTLS
250 HELP
AUTH PLAIN AGFsaWNlAHdvbmRlcmxhbmQ=
235 Authentication succeeded
MAIL FROM:<alice@susam.in>
250 OK
RCPT TO:<example.recipient@gmail.com>
250 Accepted
DATA
354 Enter message, ending with "." on a line by itself
Date: Mon, 07 Nov 2011 20:28:00 +0530
From: Alice <alice@susam.in>
To: Example Recipient <example.recipient@gmail.com>
Subject: Test email 

This is a test email.
.
250 OK id=1RNQef-0004e7-7s
QUIT
221 tesseract.susam.in closing connection
Connection closed by foreign host.

In the AUTH PLAIN line I have sent the base64 encoding of the string \0alice\0wonderland. \0 indicates a null character, alice is the sender's user name and wonderland is the sender's password. If an eavesdropper intercepts this network traffic, he can easily find the user's password by simply decoding the base64 response sent by the client. This is also susceptible to replay attacks as the eavesdropper can use the AUTH PLAIN line containing the base64 encoded credentials to log into the server in future. This is why it is very important to secure the connection with TLS or SSL while having a session with the SMTP server.

Now, let me quickly show only the relevant lines for an authentication using the LOGIN mechanism.

AUTH LOGIN
334 VXNlcm5hbWU6
YWxpY2U=
334 UGFzc3dvcmQ6
d29uZGVybGFuZA==
235 Authentication succeeded

What is happening here becomes clear by decoding the base64 encoded messages. The following statements in Python 2.7.2 decode these messages.

>>> 'VXNlcm5hbWU6'.decode('base64')
'Username:'
>>> 'YWxpY2U='.decode('base64')
'alice'
>>> 'UGFzc3dvcmQ6'.decode('base64')
'Password:'
>>> 'd29uZGVybGFuZA=='.decode('base64')
'wonderland'

If the session isn't encrypted, LOGIN authentication mechanism is susceptible to the same problems that PLAIN authentication mechanism is susceptible to. Now, let me describe the CRAM-MD5 authentication mechanism. When the client selects the CRAM-MD5 authentication mechanism, the server sends a base64 encoded challenge as shown below.

AUTH CRAM-MD5
334 PDE3ODkzLjEzMjA2NzkxMjNAdGVzc2VyYWN0LnN1c2FtLmluPg==

An HMAC is calculated for this challenge with the password as the key and MD5 as the hash function. A string is formed by concatenating the user name, a space and the hexadecimal representation of the HMAC. The base64 encoding of this string is sent as the response by the client. The following statements I tried in Python 2.7.2 show how a response can be formed for the above challenge.

>>> 'PDE3ODkzLjEzMjA2NzkxMjNAdGVzc2VyYWN0LnN1c2FtLmluPg=='.decode('base64')
'<17893.1320679123@tesseract.susam.in>'
>>> import hmac, hashlib
>>> hmac.new('wonderland', '<17893.1320679123@tesseract.susam.in>', hashlib.md5).hexdigest()
'64b2a43c1f6ed6806a980914e23e75f0'
>>> 'alice 64b2a43c1f6ed6806a980914e23e75f0'.encode('base64')
'YWxpY2UgNjRiMmE0M2MxZjZlZDY4MDZhOTgwOTE0ZTIzZTc1ZjA=\n'

Of course, this can be written as a small function:

import hmac, hashlib
def cram_md5_response(username, password, base64challenge):
    return (username + ' ' +
            hmac.new(password,
                     base64challenge.decode('base64'),
                     hashlib.md5).hexdigest()).encode('base64')

The following snippet shows the SMTP server accepting the client-response.

AUTH CRAM-MD5
334 PDE3ODkzLjEzMjA2NzkxMjNAdGVzc2VyYWN0LnN1c2FtLmluPg==
YWxpY2UgNjRiMmE0M2MxZjZlZDY4MDZhOTgwOTE0ZTIzZTc1ZjA=
235 Authentication succeeded

CRAM-MD5 authentication mechanism is relatively more secure than the other two mechanisms even if the connection is not secured because the password cannot be retrieved by decoding the base64 encoded client-response. The password is used as the key to calculate the HMAC but the password is not present anywhere in the response. It prevents replay attacks too because the server sends an unpredictable challenge for every authentication. The client-response computed for a certain challenge is invalid for further authentications which will involve different unpredictable challenges.

Here is a list of hyperlinks for further reading:

  1. RFC 4954: SMTP Service Extension for Authentication
  2. RFC 4616: The PLAIN Simple Authentication and Security Layer (SASL) Mechanism
  3. RFC 2195: IMAP/POP AUTHorize Extension for Simple Challenge/Response
  4. RFC 2104: HMAC: Keyed-Hashing for Message Authentication

2 comments

Matt Mullins said:

The problem with challenge-response authentication, however, is that it requires the server to store passwords in plaintext. There are very few servers I trust enough to store my password without properly hashing it first.

In practice, most systems use PLAIN authentication over a secured connection, such as by SSL, so that it's not susceptible to the man-in-the-middle problem described.

You can also use Kerberos through GSSAPI to trust a single source for authentication tickets. A Kerberos key distribution center does store password-equivalent data as plain-text, but it minimizes the number of systems that need to be protected to such a high degree.

Susam Pal said:

Matt, you make a very good point. Yes, the server needs to store the passwords in plaintext in case of CRAM-MD5 authentication so that it can compute the expected response and match it with the received response.

Here is a sample entry in the /etc/exim4/passwd file for the curious.

alice:$1$vRPkzzDi$3sqk2e4Jcgn/YEeB1JqgT1

In case of PLAIN or LOGIN authentication mechanism, the above entry is enough to setup the user 'alice' with password 'wonderland' that has been used in this post. In this example, the second field contains the password hashed with MD5 and a random salt. After the server receives the password from the client, it would compute a hash from the password using the same hashing algorithm and salt (vRPkzzDi in this case) and check that the result matches with the hash in this file.

For CRAM-MD5 authentication mechanism to work properly, we'll need an entry like this in the passwd file.

alice:$1$vRPkzzDi$3sqk2e4Jcgn/YEeB1JqgT1:wonderland

Strictly speaking, the second field is not necessary. It could have been empty since only the third field containing the password in plaintext is used to compute the expected response to the challenge during CRAM-MD5 authentication.

Post a comment

RSS