![]() |
Web Development »
ASP.NET »
Samples
Intermediate
RSA Interoperability between JavaScript and RSACryptoServiceProvider - Form Login ExampleBy blackinkbottleExplore the interoperability between client-side JavaScript RSA implementation and server-side RSACryptoServiceProvider in a typical form login example. |
C#, Windows, .NET 1.1, ASP.NET, VS.NET2003, IE 6.0, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||

The intention of this article is to explore the browser-server RSA interoperability by describing a way that can protect login password between the browser and the server during a typical form based authentication, while still retaining access to the plain (clear) password at the server side � for further processing, such as transferring password for the first time, changing password, performing dynamic impersonation or querying Active Directory.
The mechanism is probably well known (more on this later). The implementation given in this article focuses on the interoperability between a client-side RSA implementation using JavaScript ("RSA In JavaScript", modified in order to interoperate) and the RSA counterpart of Microsoft .NET Framework, used at the server (ASP.NET) side.
There are several common alternatives to protect login password for form based authentication, such as:
The hashing/encryption at the client side for (2) or (3) can be done in:
This article, when tackling (3), would take the JavaScript approach because it appears less obtrusive.
Encryption and decryption algorithms used would certainly be RSA. We will not discuss the plumbing of the RSA algorithm and the complete discussion on how and why RSA works is out of the scope of this article. Interested readers can find many resources available on the Internet, such as RSA Algorithm.
Let us start first by reviewing the steps and characteristics for each of the aforementioned alternatives (1) to (3).
In option (1), the user name and password are sent over the network using SSL encryption, rather than plain text. SSL would work fine though performance can get degraded particularly over slow dialup lines.
The option (2) is quite widely used and one prominent example is the standard login page from Yahoo!. When the form is submitted, the password is hashed and then sent to the server. The hashing is one-way which means it is not possible to derive the original string from the hash value. The server would compare the received value against the hash stored previously. If both match, it implies that the user has typed in the correct password and is authenticated. The hashing in Yahoo!�s case also takes the JavaScript approach, using the library from Paul Johnson. His web page Login system discusses variants that try to provide additional securities.
The option (2) however does not allow access to the raw password string at the server side, which was the motivation for devising option (3).
In option (3), as implemented in this article, the detailed flow would be:
RSACryptoServiceProvider object, loads one pre-generated public and private key (or one among a key pool). It can then be cached for reuse. public void InitCrypto(string keyFileName)
{
CspParameters cspParams = new CspParameters();
cspParams.Flags = CspProviderFlags.UseMachineKeyStore;
// To avoid repeated costly key pair generation
_sp = new RSACryptoServiceProvider(cspParams);
string path = keyFileName;
System.IO.StreamReader reader = new StreamReader(path);
string data = reader.ReadToEnd();
_sp.FromXmlString(data);
}
Note that whenever the default constructor of RSACryptoServiceProvider class is called, it automatically creates a new set of public/private key pair, ready for use. In order to re-use the previously created keys, the class is initialized with a populated CspParameters object.
private string CreateChallengeString()
{
System.Random rng = new Random(DateTime.Now.Millisecond);
// Create random string
byte[] salt = new byte[64];
for(int i=0; i<64;)
{
salt[i++] = (byte) rng.Next(65,90); // a-z
salt[i++] = (byte) rng.Next(97,122); // A-Z
}
string challenge = ComputeHashString(salt, param.D);
System.Web.HttpContext.Current.Cache.Insert(
challenge, // as cache key
salt, // as cache content, for hash verification
null,
DateTime.Now.AddMinutes(20), // valid for N mins
System.Web.Caching.Cache.NoSlidingExpiration);
return challenge;
}private string ComputeHashString(byte[] salt,
byte[] uniqueKey)
{
// Concat before hashing
byte[] target = new byte [salt.Length + uniqueKey.Length];
System.Buffer.BlockCopy(salt, 0, target, 0, salt.Length);
System.Buffer.BlockCopy(uniqueKey, 0, target,
salt.Length, uniqueKey.Length);
SHA1Managed sha = new SHA1Managed();
return StringHelper.ToBase64(sha.ComputeHash(target));
}
encryptedString(challenge + "\\" + username + "\\" + password);
Only the encrypted result is posted back to the server.
RSACryptoServiceProvider object would decrypt the postback data with the private key and split the resultant string into three different parts (if everything goes well), that is the challenge string, username and password. The server should first verify that the challenge string exists and remains un-tampered; otherwise the authentication request would be disqualified. Once the challenge string is matched, it would be invalidated and removed from the cache immediately. The server would then forward the decrypted username and password to other routines that would perform the actual validation of username-password pair � one example is to query Active Directory.
To cut the long story short, in order to complete the above design, we need to find a JavaScript implementation of RSA that works at the browser side and ensures that it can talk with Microsoft's RSA implementation in the .NET Framework at the ASP.NET server side.
The JavaScript implementation from Dave "RSA In JavaScript" seems to be working quite well on his demo page, however a test feeding the encrypted result into an RSACryptoServiceProvider instance sharing the same set of public/private key (of 1024 key length) generates a "bad data" exception.
Inspection of the JavaScript source code reveals that there is no "padding" mechanism employed.
// 0 is padded
while (a.length % key.chunkSize != 0)
{
a[i++] = 0;
}
The core RSA algorithm is a simple modular exponentiation, or rather, is a simple equation with very big numbers, therefore big numbers are crucial to ensure the strength of the algorithm. Raw RSA encryption without any padding is not secure because the number used could turn out to be small. For this reason Microsoft�s API asks for mandatory padding. Incompatible padding scheme from the JavaScript code would produce the "bad data" exception at the server side.
The JavaScript code therefore needs to implement one of two padding schemes used in the .NET RSA implementation, the first is PKCS#1 v1.5 padding and another is OAEP (PKCS#1 v2) padding.
Quoted from the Microsoft�s documentation:
Microsoft Windows 2000 or later with the high encryption pack installed. Padding: Modulus size - 11. (11 bytes is the minimum padding possible.)
Microsoft Windows XP or later. Padding: Modulus size � 2 � 2*hLen, where hLen is the size of the hash.
More research on the simpler PKCS#1 v1.5 (see here, for 8.1 Encryption-block formatting) shows how padding should be carried out. During encryption, pseudorandom nonzero bytes are generated and the final padded message (before modular exponentiation) should look like this:
0x00 || 0x02 || PseudoRandomNonZeroBytes || 0x00 || Message
After injecting the padding code (omitted for clarity, please refer to the source) to the JavaScript source, the encrypted string can then be successfully decrypted by the RSACryptoServiceProvider.
These two talked finally!
The technique, i.e. option (3), detailed in this article enables to transmit the username, and the password in encrypted form from the browser to the server, using the asymmetric RSA algorithm. Interoperability is provided by JavaScript implementation at the browser side and RSACryptoServiceProvider implementation at the server side. The form submission content is encrypted using the public key and only the server knows the corresponding private key in order to decipher the content. This avoids sending them (particularly password) in plain text and prevents man-in-the-middle attack such as eavesdropping as well as passive traffic analysis.
Using encryption alone is susceptible to replay attack. Malicious users can intercept and record the traffic and make a delayed HTTP post to masquerade as the user who entered the password. This is countered by attaching to each authentication request a challenge string. It is used once and thereafter discarded. It has a limited lifespan, those challenge strings that are issued but not used are cleaned up.
However, the mechanism does not prevent phishing. It does not prevent manipulated traffic that can have malicious JavaScript functions injected into the response received by the browser, effectively bypassing the encryption routine.
In case of encryption of a Unicode string, the program should use UTF-8 to transform Unicode characters into bytes first and Base64 to transform bytes into printable characters. This is not implemented in the source project.
The RSA interoperability can be used in many other scenarios other than a typical login page like fields on the Web form can selectively decide whether to support encryption/decryption.
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 29 Aug 2005 Editor: Rinish Biju |
Copyright 2005 by blackinkbottle Everything else Copyright © CodeProject, 1999-2009 Web17 | Advertise on the Code Project |