Coverage Report - org.trails.security.password.DigestUtil
 
Classes in this File Line Coverage Branch Coverage Complexity
DigestUtil
0% 
0% 
2.833
 
 1  
 package org.trails.security.password;
 2  
 
 3  
 import java.security.MessageDigest;
 4  
 import java.security.NoSuchAlgorithmException;
 5  
 import java.util.Random;
 6  
 
 7  
 import org.apache.commons.logging.Log;
 8  
 import org.apache.commons.logging.LogFactory;
 9  
 
 10  
 
 11  
 /**
 12  
  * A utility class for encoding a string with SHA-1 hash and comparing the equality of an encoded string
 13  
  * Uses a randomly generated salt with a default length of 2-4 (public class members, changeable if needed)
 14  
  * 
 15  
  * Implementation adapted from the examples provided at:
 16  
  * http://www.koders.com/java/fid9D416D88A1524FCC491B342D7B6A2E70694691D7.aspx
 17  
  * http://www.bombaydigital.com/arenared/2003/10/10/1
 18  
  * http://www.glenmccl.com/tip_010.htm
 19  
  * 
 20  
  */
 21  0
 public class DigestUtil {
 22  0
         private static final Log LOG = LogFactory.getLog(DigestUtil.class);
 23  
         
 24  
         public final static int SALT_MINLENGTH = 2;
 25  
         public final static int SALT_MAXLENGTH = 4;
 26  
         
 27  
         // As on Xnix 
 28  
         public static final char SALT_SEPARATOR = '$';
 29  0
         private static Random random = new Random();
 30  0
         private static MessageDigest messageDigest = null;
 31  
         
 32  
 
 33  
 
 34  
         /*
 35  
          * @return Returns true if a hash for plainTextPassword equals hash for encodedPassword. 
 36  
          * Returns false if either parameter is null or salt wasn't found from the encodedPassword 
 37  
          * @param encodedPassword containing the salt and the hashed password
 38  
          * @param plainTextPassword
 39  
          */
 40  
         public static boolean equalsEncoded(String encodedPassword, String plainTextPassword) {
 41  0
                 boolean result = false;
 42  0
                 if (encodedPassword != null && plainTextPassword != null){
 43  0
                         int index = encodedPassword.indexOf(SALT_SEPARATOR);
 44  0
                         if (index < 1) {
 45  0
                                 LOG.warn("Salt was not found from the encodedPassword parameter. Operation expects String encodedPassword, String plainTextPassword");
 46  
                         }else{
 47  0
                                 String salt = encodedPassword.substring(0, index);
 48  0
                                 result = encodedPassword.substring(index +1).equals(new String(createHash(plainTextPassword, salt.getBytes())) );
 49  
                         }
 50  
                 }
 51  0
                 return result;
 52  
         }
 53  
         
 54  
         /*
 55  
          * @return Returns an encoded password of form <salt><SALT_SEPARATOR><passwordHash> for clearTextPassword passed in as a parameter
 56  
          * Returns null if the parameter was null
 57  
          */
 58  
         public static String encode(String clearTextPassword) {
 59  0
                 String result = null;
 60  0
                 if (clearTextPassword != null){
 61  0
                         byte[] saltBytes = randomString(Math.min(SALT_MINLENGTH, SALT_MAXLENGTH), Math.max(SALT_MINLENGTH, SALT_MAXLENGTH) ).getBytes();
 62  0
                         result = new String(concatenate(saltBytes, createHash(clearTextPassword, saltBytes) ) );        
 63  
                 }
 64  0
                 return result;
 65  
         }
 66  
         
 67  
         private static byte[] createHash(String clearTextPassword, byte[] saltBytes) {
 68  
                 // kaosko: We would save the cloning cost if md was a static class member, 
 69  
                 // but it wouldn't be threadsafe. However login is already synchronized, so it should be safe to do this.
 70  0
                 if (messageDigest == null) {
 71  
                         try {
 72  0
                                 messageDigest = MessageDigest.getInstance("SHA-1");
 73  0
                         } catch (NoSuchAlgorithmException e) {
 74  0
                                 LOG.fatal("Couldn't create SHA-1 MessageDigest, password encoding doesn't work. Are you using the right version of Java?");
 75  0
                                 return null;
 76  0
                         }
 77  
                 }
 78  0
                 MessageDigest mdLocal = null;
 79  
                 try {
 80  0
                         mdLocal = (MessageDigest)messageDigest.clone();
 81  0
                 } catch (CloneNotSupportedException e1) {
 82  0
                         LOG.fatal("Couldn't clone static MessageDigest, password encoding doesn't work. Are you using the right version of Java?");
 83  0
                         return null;
 84  0
                 }
 85  
 
 86  0
                 mdLocal.update(clearTextPassword.getBytes());
 87  0
                 mdLocal.update(saltBytes);
 88  0
                 byte[] digest = mdLocal.digest();
 89  0
                 StringBuffer hexString = new StringBuffer();
 90  
                 
 91  0
                 for (int i=0;i<digest.length;i++) {
 92  0
                   hexString.append(Integer.toHexString(0xFF & digest[i]));
 93  
                 }
 94  0
                 return hexString.toString().getBytes();
 95  
 
 96  
         }
 97  
 
 98  
         /**
 99  
          * Combine two byte arrays with a salt separator in between
 100  
          */
 101  
         private static byte[] concatenate(byte[] left, byte[] right) {
 102  0
                 byte[] result = new byte[left.length + 1 + right.length];
 103  0
                 System.arraycopy(left, 0, result, 0, left.length);
 104  0
                 result[left.length] = SALT_SEPARATOR;
 105  0
                 System.arraycopy(right, 0, result, left.length + 1, right.length);
 106  0
                 return result;
 107  
         }        
 108  
         
 109  
         private static int rand(int low, int high) {
 110  0
                 int width = high - low + 1;
 111  0
                 int offset = random.nextInt() % width;
 112  0
                 if (offset < 0){
 113  0
                         offset = -offset;
 114  
                 }
 115  0
                 return low + offset;
 116  
         }
 117  
 
 118  
         private static String randomString(int low, int high) {
 119  0
                 int length = rand(low, high);
 120  0
                 byte byteArray[] = new byte[length];
 121  0
                 for (int i = 0; i < length; i++){
 122  0
                         byteArray[i] = (byte) rand('a', 'z');
 123  
                 }
 124  0
                 return new String(byteArray);
 125  
         }
 126  
 }
 127  
 
 128  
 
 129  
 
 130