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:
- RFC 4954:
SMTP Service Extension for Authentication
- RFC 4616:
The PLAIN Simple Authentication and Security Layer (SASL) Mechanism
- RFC 2195:
IMAP/POP AUTHorize Extension for Simple Challenge/Response
- RFC 2104:
HMAC: Keyed-Hashing for Message Authentication