Click here to Skip to main content
Click here to Skip to main content

RSA Interoperability between JavaScript and RSACryptoServiceProvider - Form Login Example

, 29 Aug 2005
Rate this:
Please Sign up or sign in to vote.
Explore the interoperability between client-side JavaScript RSA implementation and server-side RSACryptoServiceProvider in a typical form login example.

Introduction

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.

Background and scope

There are several common alternatives to protect login password for form based authentication, such as:

  1. User name and plain password sent over Secure Sockets Layer (SSL).
  2. Hash password at the browser side and compare the hashed value at the server side.
  3. Encrypt password at the browser side and decrypt it at the server side.

The hashing/encryption at the client side for (2) or (3) can be done in:

  • Java applet/ActiveX object, or
  • JavaScript

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).

How it works

In option (3), as implemented in this article, the detailed flow would be:

  1. The server creates a 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.

  2. When the user requests for the login page, the server generates a one-time random challenge string (in Base64), and caches it specifying N number of minutes before expiration. The form is then rendered to the browser embedding the JavaScript RSA implementation, the public key information needed for encryption and also the challenge string.
    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));
    }
  3. Upon submission after the user completes the form, the JavaScript routine would first convert the username and the password into Base64 format and then concatenate to the end of challenge string with \ as separator (since \ is not part of Base64 chars).
    encryptedString(challenge + "\\" + username + "\\" + password);

    Only the encrypted result is posted back to the server.

  4. On the server side, the 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.
  5. The cache expiration would ensure cleanup of challenge strings that are issued but never used.

    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.

Interoperability of RSA between JavaScript and the .NET Framework

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:

Direct Encryption (PKCS#1 v1.5)

Microsoft Windows 2000 or later with the high encryption pack installed. Padding: Modulus size - 11. (11 bytes is the minimum padding possible.)

OAEP padding (PKCS#1 v2)

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!

Points of interest

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.

Miscellaneous

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.

History

  • Aug 7th 2005 - first draft.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

blackinkbottle
Web Developer
Singapore Singapore
I work in the software service industry. I love to build decision support systems embracing operations research and machine intelligence.
 
In my spare time I enjoy writing interesting proof-of-concept applications in many flavors of technologies as well as reading AI in game development. I watch many films. Many titles from Criterion Collection are my beloved, from Kurosawa, Tarkovsky, Kubrick, Ozu, film noir classic, you name it.
 
Presently I stay in Singapore and is a regular in http://www.sgdotnet.org (Singapore DotNet User Group), and I also write from time to time at http://community.sgdotnet.org/blogs/blackinkbottles_ink/default.aspx. Drop by if you can.

Comments and Discussions

 
Questionbad data exception Pinmembervik19858-Aug-12 1:19 
Bug[My vote of 1] My Vote of 1 - *Never* use this code as a replacement for SSL/HTTPS PinmemberMOT73-Jul-12 1:07 
GeneralMy vote of 5 PinmemberMember 776394316-Nov-11 19:53 
GeneralMy vote of 5 Pinmemberiknowcss5-Aug-10 9:58 
AnswerRSA JavaScript classes compatible with Microsoft .NET Framework here... PinmemberEJocys25-Feb-10 11:38 
GeneralMy thanks too! [modified] PinmemberNetDave2-Oct-09 6:47 
GeneralRe: My thanks too! [modified] Pinmember_groo_12-Sep-12 22:57 
GeneralThanks Pinmembertksdia@gmail.com1-Sep-09 11:01 
Generaldecryption failed Pinmemberbrahimlippe23-Jun-09 20:47 
Generaltoo slow javascript to work Pinmemberwin32sdk30-Jul-08 15:17 
GeneralI got problem using this in Mozilla PinmemberMember 37334814-Feb-08 0:54 
GeneralInfinite loop on encryption attempt Pinmembermbouchard21@msn.com9-Oct-07 12:16 
GeneralRe: Infinite loop on encryption attempt Pinmemberchadbid10-Oct-07 5:25 
GeneralRe: Infinite loop on encryption attempt Pinmembermbouchard21@msn.com11-Oct-07 5:18 
GeneralRe: Infinite loop on encryption attempt PinmemberJohn Kenedy S.Kom8-Apr-09 15:21 
QuestionDoesn't work for passwords bigger than 123 bytes. [modified] Pinmemberasdahljsdla12-Jun-07 12:31 
GeneralGood Work Pinmembermarkuseox17-Mar-07 11:01 
Questioncan any body help me on this login page PinmemberRahmat ullah khajavi14-Feb-07 23:31 
GeneralThank you wery much! PinmemberBlinZaparaSEtimiImenami11-Jan-07 23:24 
GeneralRSA Interoperability between JavaScript and RSACryptoServiceProvider - Form Login Example Pinmemberloc_bv@yahoo.com29-Nov-06 16:46 
Generalencrytion & decrytion by java and javascripts Pinmemberloc_bv@yahoo.com28-Nov-06 23:27 
Generalencrytion & decrytion by java and javascripts Pinmemberloc_bv@yahoo.com28-Nov-06 23:26 
Generalwant to implement the same for another changing the password in the same page itself Pinmembernisha_codeproject14-Sep-06 0:09 
Generalreplay attack Pinmemberyvdh26-Apr-06 3:06 
GeneralDecryption in Java PinmemberDavidLClegg7-Oct-05 12:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.141022.2 | Last Updated 29 Aug 2005
Article Copyright 2005 by blackinkbottle
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid