Protecting Passwords with a Message Digest

Team-Fly

Having installed and compiled the Bouncy Castle cryptography package, let's try a simple example involving authentication. Computer systems often use passwords instead of digital signatures (or certificates) because they're so much easier. A password is a shared secret, which means that you know it and the server knows it, but nobody else should know it.

The Problem with Passwords

The problem with passwords is that you don't want to send them over an insecure network. Imagine, for example, that your MIDlet requires the user to sign on to a server using a user name and password. On the MID, you key in your user name and password, then click the button to send the information up to the server. Unfortunately, your data is sent as plaintext in some HTTP request. Anybody snooping on the network can easily lift your password.

Using a Message Digest

Message digests provides a way to solve this problem. Instead of sending a password as plaintext, you create a message digest value from the password and send that instead. An attacker could just steal the digest value, of course, so you add some other stuff to the digest as well so that only the server, knowing the password, can recreate the same digest value. Figure 12-1 shows the process.

click to expand
Figure 12-1: Protecting a password with a message digest

The MIDlet creates a timestamp and a random number, both of which are fed into the message digest along with the user name and the password. Then the MIDlet sends the user name, the timestamp, the random number, and the digest value up to the server. It does not send the password as cleartext, but the password is used to calculate the digest value.

The server takes the user name and looks up the corresponding password, which should be stored securely in a file or a database. Then it creates a digest value of the user name, password, timestamp, and random number. If the digest value created on the server matches the digest value sent by the client MIDlet, then the server knows that the user typed in the right password. The user has just logged in successfully.

The server needs some logic to prevent replay attacks. Specifically, the server should reject login attempts that use timestamps and random numbers that have been used before with that login. Although you could save the random numbers and timestamps of all user login attempts, it would be relatively expensive to compare each of these every time a user wanted to login. An easier way to implement this is to save the timestamp of each user's last login attempt. For each subsequent login attempt, the server looks up the saved timestamp. If the timestamp on the current attempt is later than the saved timestamp, the attempt is allowed. The current attempt's timestamp replaces the saved timestamp for this user.

Using the Bouncy Castle Cryptography Package

In the Bouncy Castle package, message digests are generically represented by the org.bouncycastle.crypto.Digest interface. You can add data into the message digest using one of two update() methods. To calculate the message digest value, call doFinal(). Specific implementations of the Digest interface are contained in the org.bouncycastle.crypto.digests package. We'll be using one called SHA1Digest, which implements the SHA-1 digest algorithm. The following line shows how to create a SHA-1 message digest object:

 Digest digest = new SHA1Digest(); 

The cryptography code is pretty simple. Most of our effort, in fact, is devoted to converting the timestamp and random number to bytes that can be pushed into the message digest object. Then it's just a matter of calling the update() method with each array of bytes.

To calculate the digest, call Digest's doFinal() method. You'll need to pass in a byte array to hold the message digest value. To find out how long this array should be, call the getDigestSize() method.

 byte[] digestValue = new byte[digest.getDigestSize()]; digest.doFinal(digestValue, 0); 

Implementing a Protected Password Protocol

This section details an implementation of protected password login. On the client side, a MIDlet collects a user name and password, as shown in Figure 12-2.


Figure 12-2: A simple form collects a user name and password.

When the Login command is invoked, the MIDlet sends data to a servlet, which determines whether or not the client is authenticated. The servlet sends back a message, which is displayed on the screen of the device, as shown in Figure 12-3.


Figure 12-3: The server says whether you're logged in or not.

The MIDlet and servlet exchange various byte arrays, such as the timestamp, the random number, and the message digest value. To make this work smoothly in the context of HTTP headers, which are plain text, the byte arrays are exchanged as hexadecimal strings. A helper class, HexCodec, handles the translation between hexadecimal strings and byte arrays. This same class is used by the MIDlet and the servlet.

Let's look at the MIDlet first. Its main screen is a form where the user can enter a user name and a password. You might be tempted to use a PASSWORD TextField, but I chose not to. For one thing, it's hard to know exactly what text you're entering . For another thing, I'm assuming that the screen of a small device is reasonably private-probably no one will be peeking over your shoulder as you enter your password.

When the user invokes the Login command, the MIDlet calculates a message digest value as described above. It assembles various parameters into an HTTP request. It then reads the response from the server and displays the response in an Alert.

The meat of the protected password algorithm is in the login() method. We create a timestamp and a random number and convert these values to byte arrays using a helper method:

 long timestamp = System.currentTimeMillis(); long randomNumber = mRandom.nextLong(); byte[] timestampBytes = getBytes(timestamp); byte[] randomBytes = getBytes(randomNumber); 

The user name and password strings, which come from the MIDlet's main form, are easily converted to byte arrays.

The entire source code for PasswordMIDlet is shown in Listing 12-1.

Listing 12-1: PasswordMIDlet, a protected password client.

start example
 import java.io.*; import java.util.Random; import javax.microedition.io.*; import javax.microedition.midlet.*; import javax.microedition.lcdui.*; import org.bouncycastle.crypto.Digest; import org.bouncycastle.crypto.digests.SHA1Digest; public class PasswordMIDlet extends MIDlet {   private Display mDisplay;   private Form mForm;   private TextField mUserField, mPasswordField;   private Random mRandom;   public void startApp() {     mDisplay = Display.getDisplay(this);     mRandom = new Random(System.currentTimeMillis());     if (mForm == null) {       mForm = new Form("Login");       mUserField = new TextField("Name", "jonathan", 32, 0);       mPasswordField = new TextField("Password", "happy8", 32, 0);       mForm.append(mUserField);       mForm.append(mPasswordField);       mForm.addCommand(new Command("Exit", Command.EXIT, 0));       mForm.addCommand(new Command("Login", Command.SCREEN, 0));       mForm.setCommandListener(new CommandListener() {         public void commandAction(Command c, Displayable s) {           if (c.getCommandType() == Command.EXIT) notifyDestroyed();           else login();         }       });     }     mDisplay.setCurrent(mForm);   }   private void login() {     // Gather the values we'll need.     long timestamp = System.currentTimeMillis();     long randomNumber = mRandom.nextLong();     String user = mUserField.getString();     byte[] userBytes = user.getBytes();     byte[] timestampBytes = getBytes(timestamp);     byte[] randomBytes = getBytes(randomNumber);     String password = mPasswordField.getString();     byte[] passwordBytes = password.getBytes();     // Create the message digest.     Digest digest = new SHA1Digest();     // Calculate the digest value.     digest.update(userBytes, 0, userBytes.length);     digest.update(timestampBytes, 0, timestampBytes.length);     digest.update(randomBytes, 0, randomBytes.length);     digest.update(passwordBytes, 0, passwordBytes.length);     byte[] digestValue = new byte[digest.getDigestSize()];     digest.doFinal(digestValue, 0);     // Create the GET URL. The hex encoded message digest value is     //   included as a parameter.     URLBuilder ub = new URLBuilder(getAppProperty("PasswordMIDlet.baseURL"));     ub.addParameter("user", user);     ub.addParameter("timestamp",         new String(HexCodec.bytesToHex(timestampBytes)));     ub.addParameter("random",         new String(HexCodec.bytesToHex(randomBytes)));     ub.addParameter("digest",         new String(HexCodec.bytesToHex(digestValue)));     String url = ub.toString();     try {       // Query the server and retrieve the response.       HttpConnection hc = (HttpConnection)Connector.open(url);       InputStream in = hc.openInputStream();       int length = (int)hc.getLength();       byte[] raw = new byte[length];       in.read(raw);       String response = new String(raw);       Alert a = new Alert("Response", response, null, null);       a.setTimeout(Alert.FOREVER);       mDisplay.setCurrent(a, mForm);       in.close();       hc.close();     }     catch (IOException ioe) {       Alert a = new Alert("Exception", ioe.toString(), null, null);       a.setTimeout(Alert.FOREVER);       mDisplay.setCurrent(a, mForm);     }   }   private byte[] getBytes(long x) {     byte[] bytes = new byte[8];     for (int i = 0; i < 8; i++)       bytes[i] = (byte)(x >> ((7 − i) * 8));     return bytes;   }   public void pauseApp() {}   public void destroyApp(boolean unconditional) {} } 
end example

The HexCodec class contains a few static methods for converting between byte arrays and hex encoded strings. The complete class is shown in Listing 12-2.

Listing 12-2: The HexCodec helper class.

start example
 public class HexCodec {   private static final char[] kDigits = {     '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',     'a', 'b', 'c', 'd', 'e', 'f'   };   public static char[] bytesToHex(byte[] raw) {     int length = raw.length;     char[] hex = new char[length * 2];     for (int i = 0; i < length; i++) {       int value = (raw[i] + 256) % 256;       int highIndex = value >> 4;       int lowIndex = value & 0x0f;       hex[i * 2 + 0] = kDigits[highIndex];       hex[i * 2 + 1] = kDigits[lowIndex];     }     return hex;   }   public static byte[] hexToBytes(char[] hex) {     int length = hex.length / 2;     byte[] raw = new byte[length];     for (int i = 0; i < length; i++) {       int high = Character.digit(hex[i * 2], 16);       int low = Character.digit(hex[i * 2 + 1], 16);       int value = (high << 4) | low;       if (value > 127) value -= 256;       raw[i] = (byte)value;     }     return raw;   }   public static byte[] hexToBytes(String hex) {     return hexToBytes(hex.toCharArray());   } } 
end example

PasswordMIDlet also uses the URLBuilder class, which provides a simple interface for assembling GET URLs. The URLBuilder class is shown in Listing 12-3.

Listing 12-3: The URLBuilder helper class.

start example
 public class URLBuilder {   private StringBuffer mBuffer;   private boolean mHasParameters;   public URLBuilder(String base) {     mBuffer = new StringBuffer(base);     mHasParameters = false;   }   public void addParameter(String name, String value) {     // Append a separator.     if (mHasParameters == false) {       mBuffer.append('?');       mHasParameters = true;     }     else       mBuffer.append('&');     // Now tack on the name and value pair. These should     //   really be URL encoded (see java.net.URLEncoder in     //   J2SE) but this class appends the name and value     //   as is, for simplicity. Names or values with spaces     //   or other special characters will not work correctly.     mBuffer.append(name);     mBuffer.append('=');     mBuffer.append(value);   }   public String toString() {     return mBuffer.toString();   } } 
end example

A simple implementation of a protected password servlet is shown in Listing 12-4.

Listing 12-4: The PasswordServlet class.

start example
 import javax.servlet.http.*; import javax.servlet.*; import java.io.*; import java.util.*; import org.bouncycastle.crypto.Digest; import org.bouncycastle.crypto.digests.SHA1Digest; public class PasswordServlet extends HttpServlet {   public void doGet(HttpServletRequest request,       HttpServletResponse response)       throws ServletException, IOException {     System.out.println("user = " + request.getParameter("user"));     System.out.println("timestamp = " + request.getParameter("timestamp"));     System.out.println("random = " + request.getParameter("random"));     System.out.println("digest = " + request.getParameter("digest"));   // Retrieve the user name.   String user = request.getParameter("user");   // Look up the password for this user.   String password = lookupPassword(user);   // Pull the timestamp and random number (hex encoded) out of   //   of the request.   String timestamp = request.getParameter("timestamp");   String randomNumber = request.getParameter("random");   // Here we would compare the timestamp with the last saved   //   timestamp for this user. We should only accept timestamps   //   that are greater than the last saved timestamp for this user.   // Gather values for the message digest.   byte[] userBytes = user.getBytes();   byte[] timestampBytes = HexCodec.hexToBytes(timestamp);   byte[] randomBytes = HexCodec.hexToBytes(randomNumber);   byte[] passwordBytes = password.getBytes();   // Create the message digest.   Digest digest = new SHA1Digest();   // Calculate the digest value.   digest.update(userBytes, 0, userBytes.length);   digest.update(timestampBytes, 0, timestampBytes.length);   digest.update(randomBytes, 0, randomBytes.length);   digest.update(passwordBytes, 0, passwordBytes.length);   byte[] digestValue = new byte[digest.getDigestSize()];   digest.doFinal(digestValue, 0);   // Now compare the digest values.   String message = "";   String clientDigest = request.getParameter("digest");   if (isEqual(digestValue, HexCodec.hexToBytes(clientDigest)))     message = "User " + user + " logged in.";   else     message = "Login was unsuccessful.";   // Send a response to the client.   response.setContentType("text/plain");   response.setContentLength(message.length());   PrintWriter out = response.getWriter();   out.println(message); }   private String lookupPassword(String user) {     // Here we would do a real lookup based on the user name.     //   We might look in a text file or a database. Here, we     //   just use a hardcoded value.     return "happy8";   }   private boolean isEqual(byte[] one, byte[] two) {     if (one.length != two.length) return false;     for (int i = 0; i < one.length; i++)       if (one[i] != two[i]) return false;     return true;   } } 
end example

The basic procedure is to pull the parameters out of the request from the MIDlet, and then independently calculate the message digest value. The servlet looks up the user's password in the lookupPassword() method. In a more serious implementation, the servlet would probably look up the password in a database of some sort.

Once the servlet figures out the user's password, it pumps the user name, password, timestamp, and random number into a message digest. Then it calculates the message digest value and compares this result with the digest value that was sent from the MIDlet. If the digest values match, the MIDlet client is authenticated.

Suggested Enhancements

One obvious enhancement to this system is to actually retrieve passwords (on the server side) from a database or password repository of some sort.

Furthermore, the servlet needs to validate the timestamp it receives from the client. Every time a user tries to login, the servlet should make sure that the user's timestamp is greater than the timestamp from the user's previous login attempt.

One possible enhancement on the client side is to store the user's name and password in a record store so they can be automatically sent with each login attempt. Normally this might seem like a bad idea. But small devices are generally kept physically secure by their owners-you try to keep your mobile phone in your possession at all times, or you lock it up somewhere. It's a trade-off between convenience and security. But just considering how difficult it is to enter text on a mobile phone keypad, you might want to give your users the convenience of using a stored name and password.

Note that the authentication performed in this scheme is per request. Each time the client sends an HTTP request to the server, it is an entirely separate conversation. Each time, therefore, the client needs to authenticate itself to the server to perform some work, it must go through the whole process again-creating a timestamp and random number, calculating a message digest, and sending the whole mess up to the server. In this system, then, you would probably add parameters to the HTTP request that specify an action or command that should be performed on behalf of the authenticated user.


Team-Fly


Wireless Java. Developing with J2ME
ColdFusion MX Professional Projects
ISBN: 1590590775
EAN: 2147483647
Year: 2000
Pages: 129

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net