Creating a Single Sign-on across ASP.NET Application and Legacy ASP Application
3/8/2004 10:01:07 AM
Encryption and hashing are used in this project to solve a common problem faced by many programmers, which is to create a single sign-on across multiple applications, which might be a mix of legacy ASP applications and ASP.NET applications.
The Scenario
For many membership websites, it is a very common scenario to limit access of
some services to members only and require users to login. But oftentimes these
different services are served under different applications or even under
different platforms. This time we want to solve the problem of making a unified
login that can be shared among legacy ASP application and ASP.NET application.
The First Thought
The first thought is always to try to dig out anything that can be utilized in
the .NET framework. The build-in FormsAuthentication class under
System.Web.Security namespace is the best candidate. By calling the
SetAuthCookie(string, bool) method in FormsAuthentication, an authentication
ticket for the given user identity can be created and attached to the cookie's
collection of the outgoing response. Later by checking the
Request.IsAuthenticated value, the user's login status can be verified. This
works very well among pure ASP.NET applications. But when it comes to ASP
applications, these methods or objects are obviously not available, or I should
say, not easy to get implemented in the same way that ASP.NET does. Since we
really don¡¯t know the detail inside FormsAuthentication or related
classes, there is no easy way we can modify or mimic these classes to make them
available to classic ASP application.
How about sharing these classes in GAC that
can be called by ASP script? The FormsAuthentication
class fails when called from ASP script
because it simply needs a FORM to be initialized. If the FormsAuthentication
and related classes can be modified, we can probably get rid of
some initialization requirements that are not needed.
The Solution
We need to make our own authentication class.
First we create the EncryptString class to serve the Obfuscation of the user's
identity as well as to verify the Obfuscated string. A triple DES encryption and
a couple of MD5 hashes are involved.
using System;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using System.Globalization;
namespace SingleLogin
{
/// <summary>
/// Provides a way to obfuscate the string.
/// </summary>
public class EncryptString
{
public EncryptString(){}
public bool Verify(string pEncryptedString)
{
return _Verify(pEncryptedString);
}
public string EncryptedString
{
get
{
return _Encrypt();
}
}
public string UserName
{
get
{
return _UserName;
}
set
{
_UserName = value;
}
}
public string Key
{
get
{
return _Key;
}
set
{
_Key = value;
}
}
private string _Encrypt()
{
MD5CryptoServiceProvider MD5 = new MD5CryptoServiceProvider();
byte[] bKey = MD5.ComputeHash(Encoding.ASCII.GetBytes(_Key));
byte[] bUserName = MD5.ComputeHash(Encoding.ASCII.GetBytes(_UserName));
byte[] eUserName = MD5.ComputeHash(_Mix(bUserName, bKey));
TripleDESCryptoServiceProvider des = new TripleDESCryptoServiceProvider();
byte[] eKey = des.CreateEncryptor(_Key192, _IV192).TransformFinalBlock(bKey, 0, bKey.Length);
return _ByteToHexString(eUserName) + _ByteToHexString(eKey);
}
private bool _Verify(string pEncryptedString)
{
try
{
byte[] eKey = _HexToByteArray(pEncryptedString.Substring(32));
byte[] eUserName = _HexToByteArray(pEncryptedString.Substring(0, 32));
MD5CryptoServiceProvider MD5 = new MD5CryptoServiceProvider();
TripleDESCryptoServiceProvider des = new TripleDESCryptoServiceProvider();
byte[] bKey = des.CreateDecryptor(_Key192, _IV192).TransformFinalBlock(eKey, 0, eKey.Length);
byte[] bUserName = MD5.ComputeHash(Encoding.ASCII.GetBytes(_UserName));
byte[] eUserName1 = MD5.ComputeHash(_Mix(bUserName, bKey));
return (_ByteToHexString(eUserName) == _ByteToHexString(eUserName1));
}
catch (CryptographicException)
{
}
catch (FormatException)
{
}
return false;
}
private byte[] _Mix(byte[] pUserName, byte[] pKey)
{
byte[] retByteArray = new byte[pUserName.Length * 2];
int i = 0, j;
foreach(byte b in pUserName)
{
j = i * 2;
retByteArray[j] = pUserName[i];
retByteArray[j+1] = pKey[i];
i++;
}
return retByteArray;
}
private string _ByteToHexString(byte[] pByteArray)
{
string retHexString = "";
foreach(byte b in pByteArray)
{
retHexString += b.ToString("x2");
}
return retHexString;
}
private static byte[] _HexToByteArray(string pHexString)
{
int length = pHexString.Length/2;
byte[] retByteArray = new byte[length];
for(int i=0; i< length; i++)
{
retByteArray[i] = Byte.Parse(pHexString.Substring(i*2, 2), NumberStyles.HexNumber);
}
return retByteArray;
}
// The key used for the triple DES encryption
private readonly byte[] _Key192 = new byte[24] {
142, 216, 90, 16, 178, 40, 8, 32,
35, 167, 34, 80, 226, 200, 125, 192,
2, 94, 51, 204, 139, 35, 14, 19};
// The Initialization Vector for the triple DES encryption
private readonly byte[] _IV192 = new byte[24] {
54, 173, 246, 179, 36, 99, 197, 3,
42, 65, 62, 38, 134, 7, 29, 123, 145,
23, 200, 58, 193, 10, 111, 232};
private string _UserName, _Key;
}
}
The process looks like the following:

The Authentication class then can be created:
using System;
using System.Web;
using System.Text;
namespace SingleLogin
{
/// <summary>
/// Summary description for Authenticate.
/// </summary>
public class Authentication
{
public Authentication()
{
}
public bool IsAuthenticated(string pEncString, string pUserName)
{
EncryptString sqs = new EncryptString();
sqs.UserName = pUserName;
return sqs.Verify(pEncString);
}
public string GetAuthenticateCode(string pUserName)
{
EncryptString sqs = new EncryptString();
sqs.UserName = pUserName;
sqs.Key = _GenerateRandomKey(8);
return sqs.EncryptedString;
}
private string _GenerateRandomKey(int pKeyLength)
{
Random random = new Random((int)DateTime.Now.Ticks);
int charLength = chars.Length;
StringBuilder sb = new StringBuilder();
for (int i=0; i< pKeyLength; i++)
{
int idx = random.Next(0, charLength);
sb.Append(chars.Substring(idx, 1));
}
return sb.ToString();
}
private const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
}
}
To test our solution in ASP application, we first register our assembly
with GAC. a reference can be found at
http://msdn.microsoft.com/msdnmag/issues/01/08/interop/default.aspx
with the part Using .NET Objects from COM.
The shortcut to register the assembly in
GAC is to locate the SingleLogin.dll that can be built in our solution (see the attached solution package) and run the
following two .NET commands:
regasm SingleLogin.dll
gacutil /i SingleLogin.dll
After that, you can run the following ASP
script on the web server to test if the correct authenticated string can be
generated based on the user's identity.
<%
Set test = Server.CreateObject("SingleLogin.Authentication")
' get the authenticate string
cookie = test.GetAuthenticateCode("demo")
response.write cookie
' test the authenticate string with the original username
name = test.IsAuthenticated(cookie, "demo")
response.write "<br>" & name
' test the authenticate string with a wrong username
name = test.IsAuthenticated(cookie, "ting")
response.write "<br>" & name
set test = nothing
%>
A similar ASP.NET web sample is also included
in the attached solution package.
When the applications share the same
authentication methods, a single sign-on can then be created among them.
Some Final Thoughts
There are some risks to authenticate users in
the way we do. For example, the authentication cookie can be hijacked and the
hijacker can reuse the cookie to authenticate himself. But this happens anyway
even if you are authenticating users with the build-in FormsAuthentication
class. Another vulnerability is the computed key can be compromised and used to
generate the authentication ticket. In this case, the attacker has to compromise
the triple DES encryption.
Still, there is plenty of space to think of for restructuring or customizing the process of
encryption. Any comments are welcome here.
References
.NET Interop: Get Ready for Microsoft .NET by
Using Wrappers to Interact with COM-based Applications
http://msdn.microsoft.com/msdnmag/issues/01/08/interop/default.aspx
Secure Query Strings: Preventing the Tampering
of Data Passed between Applications
http://www.dotnetjunkies.com/how%20to/99201486-ACFD-4607-A0CC-99E75836DC72.dcik
Download the code!
About the Author:
Ting Huang, a senior programmer for a fast growing Minnesota based online
marketing company and spends most of his time deep in system architect, ASP.NET
and SQL Server.
|