Main Page Content
Password Encryption Rationale And Java Example
Where has your password been?
Most of the web sites today have some sort of a registration module where a user is asked to choose a username/password combination. This data gets stored in the database. You might wonder if the password you provide will be kept well-protected (read encrypted). In case you are the person designing such backend registration component, why not give your users peace of mind by encrypting their passwords?One-way hash encryption
This scenario is a perfect candidate for "one-way hash encryption" also known as a message digest, digital signature, one-way encryption, digital fingerprint, or cryptographic hash. It is referred to as "one-way" because although you can calculate a message digest, given some data, you can't figure out what data produced a given message digest. This is also a collision-free mechanism that guarantees that no two different values will produce the same digest. Another property of this digest is that it is a condensed representation of a message or a data file and as such it has a fixed length. There are several message-digest algorithms used widely today.Algorithm | Strength |
---|---|
MD5 | 128 bit |
SHA-1 | 160 bit |
Typical registration scenario
Here is a typical flow of how our message digest algorithm can be used to provide one-way password hashing:1) User registers with some site by submitting the following data:username | password |
---|---|
jsmith | mypass |
username | password |
---|---|
jsmith | 5yfRRkrhJDbomacm2lsvEdg4GyY= |
plaintext password | encrypted password |
---|---|
mypass | 5yfRRkrhJDbomacm2lsvEdg4GyY= |
mypast | hXdvNSKB5Ifd6fauhUAQZ4jA7o8= |
Java code that implements one-way hash algorithm
Let's assume that you are writing a web application to be run in a servlet container. Your registration servlet might have the following portion (for clarity, I ommitted input validation steps and assume that a password value was passed in within the password form input field):[...]public void doPost(HttpServletRequest request, HttpServletResponse response){ User user = new org.myorg.registration.User(); user.setPassword(org.myorg.services.PasswordService.getInstance().encrypt(request.getParameter("password"));[...]Here is the definition of my PasswordService class that does the job of generating a one-way hash value:
package org.myorg.services;import java.io.UnsupportedEncodingException;import java.security.MessageDigest;import java.security.NoSuchAlgorithmException;import org.myorg.SystemUnavailableException;import sun.misc.BASE64Encoder;import sun.misc.CharacterEncoder;The method of interest here is encrypt(). I chose to make this class a singleton in order to ensure that there is only one instance of it at any given time to avoid concurrency issues and conflicts between generated hash values. For an explanation of this design pattern, try a google search for "java singleton pattern".Let's step through the code above to see what's going on:step 1: The registration servlet will interface with our PasswordService class using this static getInstance() method. Whenever it is invoked, a check will be made to see if an instance of this service class already exists. If so, it will be returned back to the caller (registration servlet). Otherwise, a new instance will be created.step 2: We are asking Java security API to obtain an instance of a message digest object using the algorithm supplied (in this case, SHA-1 message digest algorithm will be used. Both SHA and SHA-1 refer to the same thing, a revised SHA algorithm). Sun JDK includes JCA (Java Cryptography Architecture) which includes support for SHA algorithm. If your environment does not support SHA, NoSuchAlgorithmException will be thrown. step 3: Feed the data:public final class PasswordService
{ private static PasswordService instance;private PasswordService()
{ }public synchronized String encrypt(String plaintext) throws SystemUnavailableException
{ MessageDigest md = null; try { md = MessageDigest.getInstance("SHA"); //step 2 } catch(NoSuchAlgorithmException e) { throw new SystemUnavailableException(e.getMessage()); } try { md.update(plaintext.getBytes("UTF-8")); //step 3 } catch(UnsupportedEncodingException e) { throw new SystemUnavailableException(e.getMessage()); }byte raw[] = md.digest(); //step 4
String hash = (new BASE64Encoder()).encode(raw); //step 5 return hash; //step 6 } public static synchronized PasswordService getInstance() //step 1 { if(instance == null) { instance = new PasswordService(); } return instance; }}
a) convert the plaintext password (eg, "jsmith") into a byte-representation using UTF-8 encoding format.
b) apply this array to the message digest object created earlier. This array will be used as a source for the message digest object to operate on.step 3: Do the transformation: generate an array of bytes that represent the digested (encrypted) password value.step 4: Create a String representation of the byte array representing the digested password value. This is needed to be able to store the password in the database. At this point, the hash value of the plaintext "jsmith" is "5yfRRkrhJDbomacm2lsvEdg4GyY=".step 5: Return the String representation of the newly generated hash back to our registration servlet so that it can be stored in the database. The user.getPassword() method now returns "5yfRRkrhJDbomacm2lsvEdg4GyY="That's all. Your database password data is now encrypted and if an intruder gets a hold of it, he/she won't have much use of it. Note, you have to consider how you will handle "forgot password" functionality in this case as you now cannot simply send a password to the user's email address. (Well, you should not be doing things like that anyway) . Sounds to me like a perfect topic for my next article.