Introduction
Two fundamental qualities necessary for security assurances
over a telecommunication channel are confidentiality and
authentication. Confidentiality assures that only the right parties
have access to the communication stream, and authentication attempts to
guarantee that the parties at each end of the communication channel are
who they say they are. The Salted Challenge Response Authentication
Mechanism (SCRAM) SHA-1 is a standardized authentication technique
defined in RFC
5802. This article discusses this mechanism briefly using the
codes attached.
The primary motivation for this work was that I was unable to find a
full C# implementation of the SCRAM-SHA1 protocol for use on the
project I am currently working on and so I decided to implement it
myself. Hopefully, you wouldn’t have to go through the minor hassle of
writing a C# implementation if you ever needed it.
You are welcome :D
Background
The primary means to authenticate oneself to an entity is to
show that one holds some restricted or secret information that can be
presented when challenged. Typically, within the context of Information
and Communication Technology (ICT), one presents a user name and/or a
password that authenticates one to the challenging entity. Sometimes
the authentication process has to be mutual i.e., party A authenticates
herself to party B and party B authenticates himself to party A.
SCRAM SHA-1 belongs to a class of protocols that provide
the mutual authentication facility. Another similar protocol is the
Socialist Millionaire Protocol (SMP) discussed here
. In SCRAM SHA-1, as with most secure authentication mechanisms, the
password or secret information is never transmitted during the
execution of the protocol. Instead, the secret information in addition
to a cryptographically secure random value is used to compute another value
at each end of the communication channel. It is this computed value
that is transmitted. On receipt of the transmitted value the
communicating parties compare their computed value to the received
value. If these values match then the authentication process is
successful, otherwise it fails.
The SCRAM SHA-1 Mechanism
The SCRAM SHA-1 protocol assumes that one end of the
communication channel is the client and the other end is a server. Keep this in
mind as you read the rest of the article.
The SCRAM SHA-1 Message Sequence
Message Sequence
The execution of SCRAM SHA-1 involves the transmission and
processing of four messages; two each by/for the client and server. As
can be seen in the image above, the client begins the process by
sending the Client First Message and in response to the reception of a
validly formatted first message from the client, the server sends the
Server First Message to the client. The client processes this message
and if everything is okay, it transmits the Client Final Message. The
server, as expected, processes this message. At the end of this task,
the server should know if the client is successfully authenticated or
not. If it is, the server sends the Server Final Message, otherwise it
sends an authentication failure message to the client (or maybe not).
With the reception of the Server Final Message, the client is also able
to authenticate the server.
The SCRAM SHA-1 Cryptographic Values
There are four cryptographic values that are central to the
SCRAM SHA-1 protocol. These values are;
-
Client nonce: This is a value that is
randomly generated by the client, ideally using a cryptographic random
generator. This value, along with the client’s user name, is contained
in the Client First Message. Note that the client nonce value must be
different for each authentication session.
-
Server nonce: This is similar to the
client nonce and it is
contained in the Server First Message. This value must be different for
each authentication session and be cryptographically secure.
-
Salt: The Salt is a cryptographically
secure random number generated by the server. This Salt value and the
password are fed into a one-way cryptographic function that generates
another value. Recall from the background section that this value was
used to hide the password. The Salt is contained in the Server First
Message.
-
Iteration Count: This is a numerical
value generated by the server that indicates how many times the
cryptographic function mentioned above should be applied to the
Salt and the password to generate its output. This Iteration Count
value is transmitted in the Server First Message.
Caveat
The SCRAM SHA-1 specification strongly
advises that the
protocol should be used in conjunction with another protocol that
provides confidentiality. In other words, the SCRAM SHA-1 messages
should be exchanged over an encrypted channel. The idea is to prevent
an eavesdropper from extracting the contents of these messages in
transit and then using the values contained within to mount an off-line
dictionary attack to extract the password.
For the demo presented here, the Transport Layer Security
(TLS) is used to provide confidentiality for the authentication
protocol.
Using the code
The routines associated with the implemented SCRAM SHA-1
protocol are presented in this section. Also presented is the output
for the authentication protocol using the test values (or vectors)
given in the specification. This section ends by showing the outputs of
both the client and the server when the implemented protocol runs over
TLS.
The implemented SCRAM SHA-1 routines
string _user_name = "user";
string _password = "pencil";
string _message = string.Empty;
bool _is_server_authenticated = false;
ScramSha1 _client = new ScramSha1(USER_MODE.CLIENT, _user_name, _password);
_message = _client.GetClientFirstMessage();
_message = _client.GetClientFinalMessage(_server_first_message);
_is_server_authenticated = _client.VerifyServerSignature(_server_final_message);
if (_is_server_authenticated == true)
Console.WriteLine("The server is authenticated \n");
else
{
Console.WriteLine("The server is not authenticated \n");
}
This protocol implementation can be run in one of two modes: Server or Client. To begin the authentication process as a client, the class
constructor i.e.,
ScramSha1(USER_MODE, string,
string)
, is called. The constructor is passed
the parameters:
user mode, user name and password as shown in the code extract above.
The
Client First Message is retrieved by calling the
GetClientFirstMessage()
routine. This message
is
sent to the server. As
soon as the
Server First Message is received, the client passes it to
the
GetClientFinalMessage(string)
routine.
This routine returns the
Client Final Message which is transmitted to the server.
Finally,
the received
Server Final Message is passed to the
VerifyServerSignature(string)
routine. The
bool value returned by this
routine indicates if the server has been authenticated or not.
The implementation sets the default length of the client nonce
to 32 bytes. In order to increase the difficulty for an off-line
dictionary attack, the client might want to increase the length of this
nonce. This can be done by instantiating the class in client mode using
the constructor ScramSha1(USER_MODE, string, string, int)
,
and passing it the desired length of the client
nonce.
If an error were to occur during the processing of the
received server messages then the GetClientFirstMessage()
and GetClientFinalMessage(string)
routines return an empty string. These errors could be as result of an
improperly formatted server message.
string _user_name = string.empty;
string _password = string.empty;
string _message = string.Empty;
bool _is_client_authenticated = false;
ScramSha1 _server = new ScramSha1(USER_MODE.SERVER, _client_first_message);
_user_name = _server.GetUserName();
_message = _server.GetServerFirstMessage(_password);
_message = _server.GetServerFinalMessage(_client_final_message, ref _is_client_authenticated);
if (_is_client_authenticated == true)
{
Console.WriteLine("The client is authenticated \n");
}
else
{
Console.WriteLine("The client is not authenticated \n");
}
Executing the protocol in server mode follows the pattern
shown in the code extract above. Note that the constructor used in this
case differs from those used in the client mode. The server mode can
only be instantiated when the Client First Message has been received.
The received client message is passed to this constructor:
ScramSha1(USER_MODE, string)
. The server can
get the user name of the
client by calling the GetUserName()
routine.
The server uses this user
name to get the password from the authentication database. If it
exists, the password is passed to the GetServerFirstMessage(string)
routine. This routine returns a string containing the Server First
Message which is transmitted to the client. When the server receives
the Client Final Message, it passes it to the
GetServerFinalMessage(string ,ref bool)
routine. This routine
is also passed (by reference) a bool variable. If the bool variable
value is true
, then the client is
authenticated and the string returned
by this routine is sent to the client as can be seen in the code
extract above. Otherwise, an authentication failure message can be sent
instead.
The length of the server nonce as well as that of the Salt can
be set by calling the
ScramSha1(USER_MODE, string, int,
int)
constructor in server mode. The reason for increasing the client nonce
also applies to the server nonce and Salt.
The GetServerFirstMessage(string)
routine returns an empty string when an error occurs
during the processing of the received client message. The
GetServerFinalMessage(string ,ref
bool)
routine, on
the other hand, returns an empty string when an error occurs or when
the client is not authenticated.
In server mode, the lower and upper limits of the iteration
count can be set by calling the
SetIterationCountLimits(int,
int)
routine. The default lower and upper
limits are 4000 and 5000, respectively. It is important that these
limits are not set too high or too low. Setting it too low improves the
chances of a successful off-line dictionary attack on this
authentication mechanism. If it is set too high, it might overwhelm a
client running on a limited processor device. In order to prevent a
malicious server from degrading the performance of a client device, the
client can set the maximum allowable number of iterations it can
accommodate by passing this value to the
SetMaximumIterationCount(int)
routine. The default value
for this client maximum is 10000. If the iteration count received from
the server is above this maximum, the authentication process fails.
Executing SCRAM SHA-1 with Test Vectors
The SCRAM SHA-1 protocol RFC provides
values that are used to test the conformity of an implementation to the
authentication specification. Three things are required to run this
implementation using the test vectors: the UseTestVectors(bool)
static routine of the
class is called and passed a parameter true
to set the instance to test
mode. Secondly, the user name is set to “user”, and lastly, the
password is set to “pencil”. To see the output of the authentication
protocol in test mode, run an instance of the attached console
application, type the letter “T” and press enter. The screen should
give an output similar to that of the image below
.
SCRAM SHA-1 test vector output
Executing SCRAM SHA-1 over TLS
The TLS implementation used in this work was deliberately made
inelegant in order to keep it simple enough to demonstrate the use of
the authentication protocol over TLS.
The TLS implementation of the server is based on the SuperSocket class of Kerry Jiang. The server's SCRAM SHA-1 logic can be found in the ScramCommand
class. That of the client is contained in the SCRAMSha1TestClient
class.
To begin, an instance of the application should be run. Type
the letter “S” and press enter to start it in server mode. Then run
another instance and type the letter “C” and press enter to start it in
client mode. If everything goes according to plan you should see the
screen shots shown below.
SCRAM SHA-1 output from client perspective
SCRAM SHA-1 output from server perspective
To force the authentication process to fail you can set one
password for the client and another for the server and observe the
outputs.
History
17/12/2013: First version
26/03/2014: Made the client and the server TLS mechanisms more robust.