MD5 goodness in Java

A while back I wrote a program which would compare multiple files based on their MD5 checksums. The program can run in Command-Line mode(more powerful, supports batch processing)

md5 commandline image

and GUI mode. md5 commandline graphical User Interface image

Running via command line with the '?' flag will allow the user to view all the flags that can be used by the program. The purpose was to make sure a file hadn’t been changed without my knowledge, by keeping the old checksum and comparing it to the newly generated one.

I ran into a few hurdles along the way and thought that I would share what I encountered to give back to the developer community that has helped me a great deal. If your a Jedi Master of Java, then this probably is something you may not want to waste your time on. Otherwise, I hope this is useful.

Now on with the program. By the way, suggestions for better coding practices are appreciated 😀

The first snafu I encountered was how to actually create and use a message digest. There are quite a few resources out there, and by all means check em out. After all my research, this is what I came up with for the MD5 class

package md5IntegrityCheck;

import java.io.FileInputStream;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class MD5 {
private byte[] raw;

private String HashValue = "";

private FileIO outputToFile = new FileIO();

private MessageDigest md;

public MD5(String FileToHash) throws UnsupportedEncodingException,
NoSuchAlgorithmException {
try {
    // Obtain a message digest object.
    md = MessageDigest.getInstance("MD5");
    // Calculate the digest for the given file.
    FileInputStream in = new FileInputStream(FileToHash);
    byte[] buffer = new byte[8192];
    int length;
    while ((length = in.read(buffer)) != -1) {
        md.update(buffer, 0, length);
    }
    raw = md.digest();
    createHashValue();
    in.close();
}

catch (Exception ex) {
/**
* print to -e error path or print file in local directory called
* IntegrityCheckError.txt
*/
outputToFile.printToFile("no_E_IntegrityCheckError.txt", ex
.getMessage());
killProgram();
}

}

public MD5(String FileToHash, String ErrorPath)
throws UnsupportedEncodingException, NoSuchAlgorithmException {
try {
    // Obtain a message digest object.
    md = MessageDigest.getInstance("MD5");

    // Calculate the digest for the given file.
    FileInputStream in = new FileInputStream(FileToHash);
    byte[] buffer = new byte[8192];
    int length;
    while ((length = in.read(buffer)) != -1) {
        md.update(buffer, 0, length);
    }
    raw = md.digest();
    createHashValue();
    in.close();
} catch (Exception ex) {// failed due to write or something
    /**
    * print to -e error path or print file in local directory called
    * IntegrityCheckError.txt
    */
    if (ErrorPath.length() > 0) {
        // write to file
        outputToFile.printToFile(ErrorPath, ex.getMessage());
    }
        killProgram();
    }
}

/**
*
* @param word
*            Function sets the value of the HashValue
* @return void
*/

public String setHashValue(String word) {
    HashValue = word;
    return HashValue;
}
/**
*
* @return the Hashed value for the file
*/
public String getHashValue() {
    return HashValue;
}

public void createHashValue() {
// for (byte b : raw) { // java 5.0 syntax replace below for older 1.4
    String strBuildup="";
    for (int k = 0; k < raw.length; k++) {
        byte b = raw[k];
        String temp = Integer.toHexString(b & 0xFF);
        /*
        * toHexString has the side effect of making stuff like 0x0F only
        * one char F(when it should be '0F') so I check the length of string
        */
        if (temp.length() < 2) {
        // System.out.println("here is offending value" +temp);
        temp = "0" + temp;
        }
        temp = temp.toUpperCase();
        //this.setHashValue((HashValue + temp));
        strBuildup+=temp;
    }
    this.setHashValue((strBuildup ));

    //return HashValue;

}
/**
*
* @return the digest for the current MD5 object
*/
public byte[] getDigest() {
  return raw;
}

/**
*
* @param HashToCompare
*            -the hash number(Message Digest) to compare against
* @return flag
*/
public boolean isEqual(byte[] HashToCompare) {
    boolean flag = false;
    if (MessageDigest.isEqual(raw, HashToCompare)) {
        flag = true;
    }
    return flag;
}

/**
*
* @param Compares
*            two Hash values represented in Hex byte[] format
* @return flag
*/
public boolean isEqual(byte[] InHash, byte[] HashToCompare) {
 boolean flag = false;
   if(MessageDigest.isEqual(InHash, HashToCompare)) {
      flag = true;
   }
   return flag;
 }
/**
* Kill the program
*/
 private void killProgram() {
    System.exit(3);
  }
}

The thing that tripped me up the most was the createHashValue() method. The comments explain the problem I had using Integer.toHexString.

The second problem I had was creating a drag and drop gui. To this day, I haven't fully grasped DnD, so I used a lot of on-line resources to get it to work. It is only right that I give the code back to the world. I ended up using Java 1.6 to get the gui to work. The following is the code for handling the dropped file

package md5IntegrityCheck;

import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.File;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.List;

import javax.swing.JLabel;
import javax.swing.JTextArea;
import javax.swing.TransferHandler;

/**
* this is going to have to have an md5 object and run the code here
*
* @author javazquez
*
*/
class FileDropHandler extends TransferHandler {
/**
*
*/
private static final long serialVersionUID = 1L;

/**
*
*/

MD5 firstHash = null;

private JTextArea output;

private JLabel errorMsg;

private String fileText = "";

private boolean test = false;

private boolean same = true;

public boolean canImport(TransferSupport supp) {
    /* for the demo, we'll only support drops (not clipboard paste) */
    if (!supp.isDrop()) {
    return false;
    }

    /* return false if the drop doesn't contain a list of files */
    if (!supp.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
    return false;
    }

    /*
    * check to see if the source actions (a bitwise-OR of supported
    * actions) contains the COPY action
    */
    boolean copySupported = (COPY & supp.getSourceDropActions()) == COPY;

    /* if COPY is supported, choose COPY and accept the transfer */
    if (copySupported) {
    supp.setDropAction(COPY);
    return true;
    }

    /*
    * COPY isn't supported, so reject the transfer.
    *
    * Note: If you want to accept the transfer with the default action
    * anyway, you could instead return true.
    */
    return false;
}

public boolean importData(TransferSupport supp) {
    if (!canImport(supp)) {
    return false;
}

/* fetch the Transferable */
Transferable t = supp.getTransferable();

try {

    /* fetch the data from the Transferable */
    Object data = t.getTransferData(DataFlavor.javaFileListFlavor);

    /* data of type javaFileListFlavor is a list of files */
    List fileList = (List) data;

    /*
    * loop through the files in the file list and compare the first MD5
    * with the rest
    */

    for (int j = 0; j < fileList.size(); j++) {

        File file = (File) fileList.get(j);

        MD5 fileHashtoCompare = new MD5(file.getAbsolutePath());
        if (!test) {
        firstHash = fileHashtoCompare;
        }
        output.append(fileHashtoCompare.getHashValue() + "\t"
        + file.getName() + "\n");

        if (test) {
        if (fileHashtoCompare.isEqual(firstHash.getDigest(),
        fileHashtoCompare.getDigest())
        && same) {
        fileText = "             \t\tThese files are the same";
        } else {
        fileText = "             \t\tThese files are the DIFFERENT";
        same = false;
        }
        errorMsg.setText(fileText);
        }
        test = true;

        // System.out.println(file.getName() + fileText);

        /*
        * This is where you place your code for opening the document
        * represented by the "file" variable. For example: - create a
        * new internal frame with a text area to represent the document -
        * use a BufferedReader to read lines of the document and append
        * to the text area - add the internal frame to the desktop
        * pane, set its bounds and make it visible
        */
        }
    } catch (UnsupportedFlavorException e) {
        return false;
    } catch (IOException e) {
        return false;
    } catch (NoSuchAlgorithmException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

    return true;
}

public void setOutput(JTextArea jta) {
    output = jta;
}

public void setOutput(JLabel jta) {
    errorMsg = jta;
}

public String getText() {
    return fileText;
}

public void clearAll() {
    fileText = "";
    test = false;
    same = true;
}
}