package com.wjholden.md5test;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.net.URL;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.codec.binary.Hex;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/**
* <?xml version='1.0' encoding='ISO-8859-1' standalone='yes'?><br>
* <SecureDownload><br>
* <file name='abc.txt'><br>
* <chunk>26b6350aacc2c3105b7c5628080bae0b</chunk><br>
* <chunk>ab070fbcc4aeb75e742a3d0e71ab03db</chunk><br>
* </file><br>
* </SecureDownload><br>
* @author William John Holden (wjholden@gmail.com)
* @version 1
*/
public class Md5Test {
private static final String USAGE
= "java -classpath md5test.jar com.wjholden.md5test.Md5Test [-file|-http|-wget] [filename|uri]\n"
+ "java -classpath md5test.jar com.wjholden.md5test.Md5Test [-xmlpkg] [input] [output] [chunk size in bytes]\n"
+ "java -classpath md5test.jar com.wjholden.md5test.Md5Test [-xmlasm] [filename]\n"
+ "java -classpath md5test.jar com.wjholden.md5test.Md5Test [-xmlhttp] [uri]";
/**
* Calculate the MD5 hash of a file.
* @param filename
* @param showOutput
* @return
* @throws NoSuchAlgorithmException
* @throws IOException
*/
while (is.read() != -1)
;
is.close();
while (signature.length() < 32) {
signature = "0" + signature;
}
if (showOutput)
System.
out.
println("Digest = " + signature
);
return (signature);
}
/**
* Grabs a single file from the world wide web and calculates it's hash as it downloads.
* @param uri
* @param writeFile If true, actually save the file to the current working directory, otherwise discard data.
* @param showOutput
* @throws NoSuchAlgorithmException
* @throws IOException
*/
if (writeFile) { // save output into some file.
String filename
= uri.
substring(uri.
lastIndexOf('/') + 1);
if (showOutput)
System.
out.
println("Writing file to " + filename
);
int i;
while ((i = is.read()) != -1) {
out.write(i);
}
out.close();
} else {
while (is.read() != -1)
; // discard data downloaded
}
is.close();
while (signature.length() < 32) {
signature = "0" + signature;
}
if (showOutput)
System.
out.
println("Digest = " + signature
);
return signature;
}
/**
* Doesn't actually perform the hashes, this was just me developing a method to parse the XML.
* Use reassembleXmlPkg.
* @param filename
* @param showOutput
* @throws ParserConfigurationException
* @throws SAXException
* @throws IOException
*/
private static void hashXml
(final String filename,
boolean showOutput
) throws ParserConfigurationException, SAXException,
IOException {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
doc.getDocumentElement().normalize();
if (showOutput)
System.
out.
println("Root element of " + filename
+ " is " + doc.
getDocumentElement().
getNodeName());
NodeList listOfFiles = doc.getElementsByTagName("file");
int totalFiles = listOfFiles.getLength();
if (showOutput)
System.
out.
println("Total no of files = " + totalFiles
);
for (int s = 0; s < totalFiles; s++) {
Node fileNode = listOfFiles.item(s);
if (fileNode.getNodeType() == Node.ELEMENT_NODE) {
if (showOutput)
System.
out.
println("Node " + s
+ " is an ELEMENT_NODE.");
if (showOutput)
System.
out.
println("\t" + e.
getAttribute("name"));
NodeList chunksList = e.getElementsByTagName("chunk");
int numberOfChunks = chunksList.getLength();
if (showOutput)
System.
out.
println("\t\tNumber of chunks = " + numberOfChunks
);
for (int t = 0; t < numberOfChunks; t++) {
/*
* This is pretty lame. You have to get a list of the
* 'chunk' items before you can get the value out of the
* Node. I guess there's something I don't yet understand
* about XML (or maybe just the SAX parser).
*/
NodeList chunkListFinal = chunkElement.getChildNodes();
Node chunk = chunkListFinal.item(0);
if (showOutput)
System.
out.
println("\t\t" + chunk.
getNodeValue());
}
}
}
}
/**
* Calculate the MD5 sum of a byte array.<br><br>
* Warning: this method has a very tricky bug. If you're using this before writing a
* byte array to file/network, make sure you don't give this method a longer array than
* you need or the hash will be different.<br>
* byte tooBig[8] = "Hello".getBytes()<br>
* byte justRight[6] = "Hello".getBytes()<br>
* Hashing the two arrays shown above will result in different results. Stay safe.
* @param array
* @return
* @throws NoSuchAlgorithmException
*/
String signature
= new String(Hex.
encodeHex(md.
digest(array
)));
return signature;
}
/**
* Build an XML file AND split inputFile into files where filename is the same as their MD5 hash.
* @param inputFile
* @param outputFile
* @param chunksize
* @param showOutput
* @throws IOException
* @throws NoSuchAlgorithmException
*/
private static void createXmlPackage
(final String inputFile,
final String outputFile,
final int chunksize,
boolean showOutput
) throws IOException,
long filesize = in.length();
double numberOfChunksDouble = ((double) filesize) / ((double) chunksize);
int numberOfChunks
= (int) (Math.
ceil(numberOfChunksDouble
));
if (showOutput)
System.
out.
println(inputFile
+ " is " + filesize
+ " bytes, with chunk size " + chunksize
+ " this results in " + numberOfChunks
+ " chunks.");
xmlout.write("<?xml version='1.0' encoding='ISO-8859-1' standalone='yes'?>\n".getBytes());
xmlout.write("<SecureDownload>\n".getBytes());
xmlout.write(("\t<file name='" + inputFile + "'>\n").getBytes());
for (int i = 0; i < numberOfChunks; i++) {
if (showOutput)
System.
out.
print("Chunk " + i
+ "\t");
int readBytes = 0;
int c;
byte chunk[] = new byte[chunksize];
while (readBytes < chunksize && (c = is.read()) != -1) {
// notice that the cast does not occur until here.
// If c was type byte then the check for -1 causes
// strange behavior on binary files.
chunk[readBytes++] = (byte) c;
}
if (showOutput)
System.
out.
print(readBytes
+ " bytes\t");
// due to caveat with hashByteArray method, create new byte array of length equal to number of bytes read.
byte hashArray[] = new byte[readBytes];
for (int k=0; k<readBytes; k++)
{
hashArray[k] = chunk[k];
}
String signature
= hashByteArray
(hashArray
);
if (showOutput)
System.
out.
println(signature
);
xmlout.write(("\t\t<chunk>" + signature + "</chunk>\n").getBytes());
int writeBytes = 0;
while (writeBytes < hashArray.length) {
out.write(hashArray[writeBytes++]);
}
out.close();
}
is.close();
xmlout.write("\t</file>\n</SecureDownload>\n\n".getBytes());
xmlout.close();
}
/**
* On the local filesystem, reassemble a file that was previously broken
* up by createXmlPackage, where xmlpkg is the filename of the XML file.
* @param xmlpkg
* @param showOutput
* @throws ParserConfigurationException
* @throws SAXException
* @throws IOException
* @throws NoSuchAlgorithmException
*/
private static void reassembleXmlPkg
(final String xmlpkg,
boolean showOutput
) throws ParserConfigurationException, SAXException,
IOException,
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
doc.getDocumentElement().normalize();
if (showOutput)
System.
out.
println("Root element of " + xmlpkg
+ " is " + doc.
getDocumentElement().
getNodeName());
NodeList listOfFiles = doc.getElementsByTagName("file");
int totalFiles = listOfFiles.getLength();
if (showOutput)
System.
out.
println("Total no of files = " + totalFiles
);
for (int s = 0; s < totalFiles; s++) {
Node fileNode = listOfFiles.item(s);
if (fileNode.getNodeType() == Node.ELEMENT_NODE) {
if (showOutput)
System.
out.
println("Node " + s
+ " is an ELEMENT_NODE.");
if (showOutput)
System.
out.
println("\t" + e.
getAttribute("name"));
NodeList chunksList = e.getElementsByTagName("chunk");
int numberOfChunks = chunksList.getLength();
if (showOutput)
System.
out.
println("\t\tNumber of chunks = " + numberOfChunks
);
for (int t = 0; t < numberOfChunks; t++) {
/*
* This is pretty lame. You have to get a list of the
* 'chunk' items before you can get the value out of the
* Node. I guess there's something I don't yet understand
* about XML (or maybe just the SAX parser).
*/
NodeList chunkListFinal = chunkElement.getChildNodes();
Node chunk = chunkListFinal.item(0);
if (showOutput)
System.
out.
println("\t\t" + chunk.
getNodeValue());
String myMd5
= chunk.
getNodeValue();
if (myMd5.equals(hashFile(myMd5, false))) {
int c;
while ((c = is.read()) != -1) {
out.write(c);
}
} else {
System.
err.
println(myMd5
+ " failed hashing check. Aborting write.");
return;
}
}
out.close();
}
}
}
/**
* This is mostly a copy-and-paste job from my earlier hashHttp and reassembleXmlPkg methods.
* @param uri
* @param showOutput
* @throws ParserConfigurationException
* @throws SAXException
* @throws IOException
* @throws NoSuchAlgorithmException
*/
private static void reassembleXmlPkgFromURI
(final String uri,
boolean showOutput
) throws ParserConfigurationException, SAXException,
IOException,
// Download the XML file, then parse what you receive.
String xmlpkg
= uri.
substring(uri.
lastIndexOf('/') + 1);
int c;
while ((c = is.read()) != -1)
{
out.write(c);
}
out.close();
is.close();
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
doc.getDocumentElement().normalize();
if (showOutput)
System.
out.
println("Root element of " + xmlpkg
+ " is " + doc.
getDocumentElement().
getNodeName());
NodeList listOfFiles = doc.getElementsByTagName("file");
int totalFiles = listOfFiles.getLength();
if (showOutput)
System.
out.
println("Total no of files = " + totalFiles
);
for (int s = 0; s < totalFiles; s++) {
Node fileNode = listOfFiles.item(s);
if (fileNode.getNodeType() == Node.ELEMENT_NODE) {
if (showOutput)
System.
out.
println("Node " + s
+ " is an ELEMENT_NODE.");
if (showOutput)
System.
out.
println("\t" + e.
getAttribute("name"));
NodeList chunksList = e.getElementsByTagName("chunk");
int numberOfChunks = chunksList.getLength();
if (showOutput)
System.
out.
println("\t\tNumber of chunks = " + numberOfChunks
);
for (int t = 0; t < numberOfChunks; t++) {
NodeList chunkListFinal = chunkElement.getChildNodes();
Node chunk = chunkListFinal.item(0);
if (showOutput)
System.
out.
println("\t\t" + chunk.
getNodeValue());
String myMd5
= chunk.
getNodeValue();
// Use hashHttp to download and hash chunk at once.
String myUri
= uri.
substring(0, uri.
lastIndexOf('/') + 1) + myMd5
;
if (showOutput)
System.
out.
println("Downloading and hashing " + myUri
);
if (myMd5.equals(hashHttp(myUri, true, false))) {
int c1;
while ((c1 = is1.read()) != -1) {
out1.write(c1);
}
} else {
System.
err.
println(myMd5
+ " failed hashing check. Aborting write.");
return;
}
}
out1.close();
}
}
}
public static void main
(String[] args
) {
try {
if (args.length == 2 && "-file".equals(args[0])) {
hashFile(args[1], true);
} else if (args.length == 2 && "-http".equals(args[0])) {
hashHttp(args[1], false, true);
} else if (args.length == 2 && "-wget".equals(args[0])) {
hashHttp(args[1], true, true);
} else if (args.length == 2 && "-xml".equals(args[0])) {
hashXml(args[1], true);
} else if (args.length == 4 && "-xmlpkg".equals(args[0])) {
createXmlPackage
(args
[1], args
[2],
Integer.
parseInt(args
[3]),
true);
} else if (args.length == 2 && "-xmlasm".equals(args[0])) {
reassembleXmlPkg(args[1], true);
} else if (args.length == 2 && "-xmlhttp".equals(args[0])) {
reassembleXmlPkgFromURI(args[1], true);
} else {
}
e.printStackTrace();
}
}
}