Passwords are freely viewable over the Internet, and can be skimmed easily. Public/private key encryption helps out by creating an encrypted code at the time the user wants to log in.
Two keys are involved in this process. In this discussion, the password is the private key and the public key is a random string sent from the server. Here’s how it works: Say the password is opensesame. When the user logs in, that password could simply be sent, using the GET or POST method, to the server, which exposes it publicly. That means malicious persons could skim it and use it to log in themselves.
However, you could change that exposure cleverly at login time by having the code on the server send a random text string, say, abcd. Then you combine the password and the random string to get, for example, opensesameabcd. Next, you can use an algorithm, such as the MD5 algorithm, to encrypt the resulting string, opensesameabcd. That gives you a string of bytes that you can send to the server.
That string of bytes may be skimmed as well, but it won’t do malicious people much good because the next time they try to log in, the random string will have changed, so the string of bytes they’ve skimmed won’t work.
It’s not a perfect scheme-especially if the malicious entity can read the random string public key and figure out how it’s used-but it does provide some measure of protection.
Here’s an example showing how to work with MD5-encoded text strings in the browser and server. This example takes a simple text string-test-and encodes it, sending it up to the server. The server also encodes the same text string and compares the two strings; if they match, a confirming message is sent to the browser.
There are various MD5 encryption tools available on the Internet. This example uses the JavaScript library, webtoolkit.md5.js (available at www.webtoolkit.info/javascript/utils/md5/index.html), which it includes in the page md5.html:
<html> <head> <title>Using public and private keys</title> <script src="/books/1/252/1/html/2/webtoolkit.md5.js"></script> . . .
Then md5.html displays a button with the caption Check encrypted text:
<body> <H1>Using public and private keys</H1> <form> <input type = "button" value = "Check encrypted text" . . . </form> <div > <p>The fetched message will appear here.</p> </div> </body> </html>
and that button is connected to a function named getData, which calls the JSP document md5.jsp on the server:
<body> <H1>Using public and private keys</H1> <form> <input type = "button" value = "Check encrypted text" onclick = "getData('md5.jsp', 'targetDiv')"> </form> <div > <p>The fetched message will appear here.</p> </div> </body> </html>
The JavaScript part of the page starts by using the webtoolkit.md5.js library’s MD5 method to encode the text string test into a string of 32 bytes, md5String:
<html> <head> <title>Using public and private keys</title> <script src="/books/1/252/1/html/2/webtoolkit.md5.js"></script> <script language = "javascript"> var md5String = MD5("test"); . . .
Then the code creates the XMLHttpRequest object it uses to communicate with the server:
<html> <head> <title>Using public and private keys</title> <script src="/books/1/252/1/html/2/webtoolkit.md5.js"></script> <script language = "javascript"> var md5String = MD5("test"); var XMLHttpRequestObject = false; if (window.XMLHttpRequest) { XMLHttpRequestObject = new XMLHttpRequest(); } else if (window.ActiveXObject) { XMLHttpRequestObject = new ActiveXObject("Microsoft.XMLHTTP"); } . . .
The getData function, called when the button is clicked, sets up the call to md5.jsp, using the POST method:
function getData(dataSource, divID) { if(XMLHttpRequestObject) { var obj = document.getElementById(divID); XMLHttpRequestObject.open("POST", dataSource); XMLHttpRequestObject.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); XMLHttpRequestObject.onreadystatechange = function() { if (XMLHttpRequestObject.readyState == 4 && XMLHttpRequestObject.status == 200) { obj.innerHTML = XMLHttpRequestObject.responseText; } } . . . } }
The data to send is the MD5-encoded string, which the getData function sends under the parameter name data:
function getData(dataSource, divID) { if(XMLHttpRequestObject) { var obj = document.getElementById(divID); XMLHttpRequestObject.open("POST", dataSource); XMLHttpRequestObject.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); XMLHttpRequestObject.onreadystatechange = function() { if (XMLHttpRequestObject.readyState == 4 && XMLHttpRequestObject.status == 200) { obj.innerHTML = XMLHttpRequestObject.responseText; } } XMLHttpRequestObject.send("data=" + md5String); } }
Now it’s up to the JSP page, md5.jsp, to encrypt the text string test and compare it to what’s been sent from the browser. In Java, you can use the java.security.MessageDigest package to encrypt MD5 strings, so you first start by importing that package to make it available to your code:
<%@ page import="java.security.MessageDigest" %> <html> <head> <title> Using public and private keys </title> </head> . . .
Next, you recover the MD5 string the browser sent, storing it in a variable named browserString like this:
<%@ page import="java.security.MessageDigest" %> <html> <head> <title> Using public and private keys </title> </head> <body> <% String browserString = request.getParameter("data"); . . .
Now it’s time to encode the text test, which you start by getting a MessageDigest object-you enclose this code in a Java try block, which is used to contain sensitive code that could cause problems:
<% String browserString = request.getParameter("data"); try { MessageDigest md = MessageDigest.getInstance("MD5"); . . . } . . .
You can encode the string test into an MD5 string using the MessageDigest object’s digest method. To do that, you need to convert the string test into an array of bytes, which you can do with the Java String class’s getBytes method this way:
<% String browserString = request.getParameter("data"); byte[] bytes = {0, 0, 0}; try { MessageDigest md = MessageDigest.getInstance("MD5"); bytes = md.digest("test".getBytes()); } . . .
If there was an error, you can handle it with a catch block, which follows the try block and displays an error message like this:
String browserString = request.getParameter("data"); String serverString = new String(""); byte[] bytes = {0, 0, 0}; try { MessageDigest md = MessageDigest.getInstance("MD5"); bytes = md.digest("test".getBytes()); } catch(Exception ex) { out.println("Error."); } . . .
That stores the MD5 string as an array of bytes. Now you’ve got to compare those bytes against the string of bytes sent to you from the browser. For example, the text sent to you from the browser might look like this: 09F8E3..., where you have byte values that have been converted to hexa-decimal, then to strings, and concatenated together. On the server, you have the bytes 09, F8, E3, and so on, in an array of numerical, not string, bytes.
One way of comparing these two sets of data is to convert the array of bytes into a string like the one sent from the browser. To do that, you might start by creating such a string, serverString, like this, where you loop over all the bytes in the byte array:
<% String browserString = request.getParameter("data"); String serverString = new String(""); byte[] bytes = {0, 0, 0}; try { MessageDigest md = MessageDigest.getInstance("MD5"); bytes = md.digest("test".getBytes()); } catch(Exception ex) { out.println("Error."); } for (int loopIndex = 0; loopIndex < bytes.length; loopIndex++){ . . . } . . .
Converting an array of bytes to a string of hexadecimal entries takes some work in Java. You can start by getting the current byte from the byte array and converting it into an Integer object:
<% String browserString = request.getParameter("data"); String serverString = new String(""); byte[] bytes = {0, 0, 0}; try { MessageDigest md = MessageDigest.getInstance("MD5"); bytes = md.digest("test".getBytes()); } catch(Exception ex) { out.println("Error."); } for (int loopIndex = 0; loopIndex < bytes.length; loopIndex++){ byte b = bytes[loopIndex]; Integer i = new Integer(b); . . . } . . .
What does that buy you? The Integer class has a handy method named toHexString that converts an integer into a hexadecimal string, so here’s how you convert the current byte into hex string representation (for example, the numeric byte 4D will be converted into the text 4D):
<% String browserString = request.getParameter("data"); . . . for (int loopIndex = 0; loopIndex < bytes.length; loopIndex++){ byte b = bytes[loopIndex]; Integer i = new Integer(b); String s = Integer.toHexString(i.intValue()); . . . } . . .
You have to be a little careful here because each byte must make up two characters in the serverString variable, and the current byte might be 9 or less-which means its string would only be one character (for example, 9 would be translated into 9, not 09). To get around that, you add a leading 0 if needed:
<% String browserString = request.getParameter("data"); . . . for (int loopIndex = 0; loopIndex < bytes.length; loopIndex++){ byte b = bytes[loopIndex]; Integer i = new Integer(b); String s = Integer.toHexString(i.intValue()); if(java.lang.Math.abs(i.intValue()) < 10){ s = "0" + s; } . . . } . . .
In fact, there’s something else to be careful about: if the current byte holds a value greater than 7F, it’ll be treated as a negative value by the toHexString, which means that the string returned by that method will end up containing six leading hex F digits-for example, E8 would be converted to the string FFFFFFE8, following the standard binary representation for negative numbers. To fix that, you can strip off the leading hex F digits this way:
<% String browserString = request.getParameter("data"); . . . for (int loopIndex = 0; loopIndex < bytes.length; loopIndex++){ byte b = bytes[loopIndex]; Integer i = new Integer(b); String s = Integer.toHexString(i.intValue()); if(java.lang.Math.abs(i.intValue()) < 10){ s = "0" + s; } if(s.indexOf("ffffff") >= 0){ s = s.substring(6); } . . . } . . .
You can then append the current byte’s string representation to the serverString variable, finishing the for loop. Then you check whether the resulting string, serverString, matches the browserString variable, using the Java String class’s equals method:
String browserString = request.getParameter("data"); . . . for (int loopIndex = 0; loopIndex < bytes.length; loopIndex++){ byte b = bytes[loopIndex]; Integer i = new Integer(b); String s = Integer.toHexString(i.intValue()); if(java.lang.Math.abs(i.intValue()) < 10){ s = "0" + s; } if(s.indexOf("ffffff") >= 0){ s = s.substring(6); } serverString += s; } if(browserString.equals(serverString)){ . . . }
If the browser string matches the server string, you can display the confirming text You're in in the browser; otherwise, you can display an error:
String browserString = request.getParameter("data"); . . . for (int loopIndex = 0; loopIndex < bytes.length; loopIndex++){ byte b = bytes[loopIndex]; Integer i = new Integer(b); String s = Integer.toHexString(i.intValue()); if(java.lang.Math.abs(i.intValue()) < 10){ s = "0" + s; } if(s.indexOf("ffffff") >= 0){ s = s.substring(6); } serverString += s; } if(browserString.equals(serverString)){ out.println("You're in."); } else { out.println("No go."); }
Now md5.html in the browser will encrypt the string test and send it to the server, which will check to see whether that string matches the MD5 encryption of test as it should.
You can see md5.html at work in Figure 15.15. When the user clicks the Check encrypted text button, this page MD5-encrypts test and sends it to the JSP md5.jsp.
Figure 15.15: The md5.html page
After the encrypted string is sent to md5.jsp, that JSP returns the text You're in, as shown in Figure 15.16.
Figure 15.16: The two MD5 strings agree.
This example demonstrates a way to send MD5 data between browser and server. In practice, you use this technique to handle passwords. The server sends a random string to the browser, which combines that string with the password, encrypts it, and sends the result back to the server for checking-which means that the naked password is never sent over the Internet.