/* * MD5 implementation * written Santeri Paavolainen, Helsinki Finland 1996 * (c) Santeri Paavolainen, Helsinki Finland 1996 * modifications Copyright (C) 2002-2004 Stephen Ostermiller * http://ostermiller.org/contact.pl?regarding=Java+Utilities * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * See COPYING.TXT for details. * * The original work by Santeri Paavolainen can be found at * http://www.helsinki.fi/~sjpaavol/programs/md5/ * * This Java class has been derived from the RSA Data Security, Inc. MD5 * Message-Digest Algorithm and its reference implementation. */ package ctmobup.utils; import java.io.*; /** * MD5 hash generator. * More information about this class is available from ostermiller.org. *
* This class takes as input a message of arbitrary length and produces * as output a 128-bit "fingerprint" or "message digest" of the input. * It is conjectured that it is computationally infeasible to produce * two messages having the same message digest, or to produce any * message having a given pre-specified target message digest. The MD5 * algorithm is intended for digital signature applications, where a * large file must be "compressed" in a secure manner before being * encrypted with a private (secret) key under a public-key cryptosystem * such as RSA. *
* For more information see RFC1321. * * @see MD5OutputStream * @see MD5InputStream * * @author Santeri Paavolainen http://www.helsinki.fi/~sjpaavol/programs/md5/ * @author Stephen Ostermiller http://ostermiller.org/contact.pl?regarding=Java+Utilities * @since ostermillerutils 1.00.00 */ public class MD5 { /** * Class constructor * * @since ostermillerutils 1.00.00 */ public MD5 () { reset(); } /** * Gets this hash sum as an array of 16 bytes. * * @return Array of 16 bytes, the hash of all updated bytes. * * @since ostermillerutils 1.00.00 */ public byte[] getHash() { if (!finalState.valid) { finalState.copy(workingState); long bitCount = finalState.bitCount; // Compute the number of left over bits int leftOver = (int) (((bitCount >>> 3)) & 0x3f); // Compute the amount of padding to add based on number of left over bits. int padlen = (leftOver < 56) ? (56 - leftOver) : (120 - leftOver); // add the padding update(finalState, padding, 0, padlen); // add the length (computed before padding was added) update(finalState, encode(bitCount), 0, 8); finalState.valid = true; } // make a copy of the hash before returning it. return encode(finalState.state, 16); } /** * Returns 32-character hex representation of this hash. * * @return String representation of this object's hash. * * @since ostermillerutils 1.00.00 */ public String getHashString(){ return toHex(this.getHash()); } /** * Gets the MD5 hash of the given byte array. * * @param b byte array for which an MD5 hash is desired. * @return Array of 16 bytes, the hash of all updated bytes. * * @since ostermillerutils 1.00.00 */ public static byte[] getHash(byte[] b){ MD5 md5 = new MD5(); md5.update(b); return md5.getHash(); } /** * Gets the MD5 hash of the given byte array. * * @param b byte array for which an MD5 hash is desired. * @return 32-character hex representation the data's MD5 hash. * * @since ostermillerutils 1.00.00 */ public static String getHashString(byte[] b){ MD5 md5 = new MD5(); md5.update(b); return md5.getHashString(); } /** * Gets the MD5 hash the data on the given InputStream. * * @param in byte array for which an MD5 hash is desired. * @return Array of 16 bytes, the hash of all updated bytes. * @throws IOException if an I/O error occurs. * * @since ostermillerutils 1.00.00 */ public static byte[] getHash(InputStream in) throws IOException { MD5 md5 = new MD5(); byte[] buffer = new byte[1024]; int read; while ((read = in.read(buffer)) != -1){ md5.update(buffer, read); } return md5.getHash(); } /** * Gets the MD5 hash the data on the given InputStream. * * @param in byte array for which an MD5 hash is desired. * @return 32-character hex representation the data's MD5 hash. * @throws IOException if an I/O error occurs. * * @since ostermillerutils 1.00.00 */ public static String getHashString(InputStream in) throws IOException { MD5 md5 = new MD5(); byte[] buffer = new byte[1024]; int read; while ((read = in.read(buffer)) != -1){ md5.update(buffer, read); } return md5.getHashString(); } /** * Gets the MD5 hash of the given String. * The string is converted to bytes using the current * platform's default character encoding. * * @param s String for which an MD5 hash is desired. * @return Array of 16 bytes, the hash of all updated bytes. * * @since ostermillerutils 1.00.00 */ public static byte[] getHash(String s){ MD5 md5 = new MD5(); md5.update(s); return md5.getHash(); } /** * Gets the MD5 hash of the given String. * The string is converted to bytes using the current * platform's default character encoding. * * @param s String for which an MD5 hash is desired. * @return 32-character hex representation the data's MD5 hash. * * @since ostermillerutils 1.00.00 */ public static String getHashString(String s){ MD5 md5 = new MD5(); md5.update(s); return md5.getHashString(); } /** * Gets the MD5 hash of the given String. * * @param s String for which an MD5 hash is desired. * @param enc The name of a supported character encoding. * @return Array of 16 bytes, the hash of all updated bytes. * @throws UnsupportedEncodingException If the named encoding is not supported. * * @since ostermillerutils 1.00.00 */ public static byte[] getHash(String s, String enc) throws UnsupportedEncodingException { MD5 md5 = new MD5(); md5.update(s, enc); return md5.getHash(); } /** * Gets the MD5 hash of the given String. * * @param s String for which an MD5 hash is desired. * @param enc The name of a supported character encoding. * @return 32-character hex representation the data's MD5 hash. * @throws UnsupportedEncodingException If the named encoding is not supported. * * @since ostermillerutils 1.00.00 */ public static String getHashString(String s, String enc) throws UnsupportedEncodingException { MD5 md5 = new MD5(); md5.update(s, enc); return md5.getHashString(); } /** * Reset the MD5 sum to its initial state. * * @since ostermillerutils 1.00.00 */ public void reset() { workingState.reset(); finalState.valid = false; } /** * Returns 32-character hex representation of this hash. * * @return String representation of this object's hash. * * @since ostermillerutils 1.00.00 */ public String toString(){ return getHashString(); } /** * Update this hash with the given data. *
* A state may be passed into this method so that we can add padding * and finalize a md5 hash without limiting our ability to update * more data later. *
* If length bytes are not available to be hashed, as many bytes as * possible will be hashed. * * @param state Which state is updated. * @param buffer Array of bytes to be hashed. * @param offset Offset to buffer array. * @param length number of bytes to hash. * * @since ostermillerutils 1.00.00 */ private void update (MD5State state, byte buffer[], int offset, int length) { finalState.valid = false; // if length goes beyond the end of the buffer, cut it short. if ((length + offset) > buffer.length){ length = buffer.length - offset; } // compute number of bytes mod 64 // this is what we have sitting in a buffer // that have not been hashed yet int index = (int) (state.bitCount >>> 3) & 0x3f; // add the length to the count (translate bytes to bits) state.bitCount += length << 3; int partlen = 64 - index; int i = 0; if (length >= partlen) { System.arraycopy(buffer, offset, state.buffer, index, partlen); transform(state, decode(state.buffer, 64, 0)); for (i = partlen; (i + 63) < length; i+= 64){ transform(state, decode(buffer, 64, i)); } index = 0; } // buffer remaining input if (i < length) { for (int start = i; i < length; i++) { state.buffer[index + i - start] = buffer[i + offset]; } } } /** * Update this hash with the given data. *
* If length bytes are not available to be hashed, as many bytes as * possible will be hashed. * * @param buffer Array of bytes to be hashed. * @param offset Offset to buffer array. * @param length number of bytes to hash. * * @since ostermillerutils 1.00.00 */ public void update (byte buffer[], int offset, int length) { update(workingState, buffer, offset, length); } /** * Update this hash with the given data. *
* If length bytes are not available to be hashed, as many bytes as
* possible will be hashed.
*
* @param buffer Array of bytes to be hashed.
* @param length number of bytes to hash.
*
* @since ostermillerutils 1.00.00
*/
public void update (byte buffer[], int length) {
update(buffer, 0, length);
}
/**
* Update this hash with the given data.
*
* @param buffer Array of bytes to be hashed.
*
* @since ostermillerutils 1.00.00
*/
public void update (byte buffer[]) {
update(buffer, 0, buffer.length);
}
/**
* Updates this hash with a single byte.
*
* @param b byte to be hashed.
*
* @since ostermillerutils 1.00.00
*/
public void update (byte b) {
byte buffer[] = new byte[1];
buffer[0] = b;
update(buffer, 1);
}
/**
* Update this hash with a long.
* This hash will be updated in a little endian order with the
* the least significant byte going first.
*
* @param l long to be hashed.
*
* @since ostermillerutils 1.00.00
*/
private void update (MD5State state, long l) {
update(
state,
new byte[] {
(byte)((l >>> 0) & 0xff),
(byte)((l >>> 8) & 0xff),
(byte)((l >>> 16) & 0xff),
(byte)((l >>> 24) & 0xff),
(byte)((l >>> 32) & 0xff),
(byte)((l >>> 40) & 0xff),
(byte)((l >>> 48) & 0xff),
(byte)((l >>> 56) & 0xff),
},
0,
8
);
}
/**
* Update this hash with a String.
* The string is converted to bytes using the current
* platform's default character encoding.
*
* @param s String to be hashed.
*
* @since ostermillerutils 1.00.00
*/
public void update (String s) {
update(s.getBytes());
}
/**
* Update this hash with a String.
*
* @param s String to be hashed.
* @param enc The name of a supported character encoding.
* @throws UnsupportedEncodingException If the named encoding is not supported.
*
* @since ostermillerutils 1.00.00
*/
public void update (String s, String enc) throws UnsupportedEncodingException {
update(s.getBytes(enc));
}
/**
* The current state from which the hash sum
* can be computed or updated.
*
* @since ostermillerutils 1.00.00
*/
private MD5State workingState = new MD5State();
/**
* Cached copy of the final MD5 hash sum. This is created when
* the hash is requested and it is invalidated when the hash
* is updated.
*
* @since ostermillerutils 1.00.00
*/
private MD5State finalState = new MD5State();
/**
* Temporary buffer cached here for performance reasons.
*
* @since ostermillerutils 1.00.00
*/
private int[] decodeBuffer = new int[16];
/**
* 64 bytes of padding that can be added if the length
* is not divisible by 64.
*
* @since ostermillerutils 1.00.00
*/
private static final byte padding[] = {
(byte) 0x80, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
};
/**
* Contains internal state of the MD5 class.
* Passes MD5 test suite as defined in RFC1321.
*
* @since ostermillerutils 1.00.00
*/
private class MD5State {
/**
* True if this state is valid.
*
* @since ostermillerutils 1.00.00
*/
public boolean valid = true;
/**
* Reset to initial state.
*
* @since ostermillerutils 1.00.00
*/
public void reset(){
state[0] = 0x67452301;
state[1] = 0xefcdab89;
state[2] = 0x98badcfe;
state[3] = 0x10325476;
bitCount = 0;
}
/**
* 128-byte state
*
* @since ostermillerutils 1.00.00
*/
public int state[] = new int[4];
/**
* 64-bit count of the number of bits that have been hashed.
*
* @since ostermillerutils 1.00.00
*/
public long bitCount;
/**
* 64-byte buffer (512 bits) for storing to-be-hashed characters
*
* @since ostermillerutils 1.00.00
*/
public byte buffer[] = new byte[64];
public MD5State() {
reset();
}
/**
* Set this state to be exactly the same as some other.
*
* @param from state to copy from.
*
* @since ostermillerutils 1.00.00
*/
public void copy(MD5State from) {
System.arraycopy(from.buffer, 0, this.buffer, 0, this.buffer.length);
System.arraycopy(from.state, 0, this.state, 0, this.state.length);
this.valid = from.valid;
this.bitCount = from.bitCount;
}
public String toString(){
return state[0] + " " + state[1] + " " + state[2] + " " + state[3];
}
}
/**
* Turns array of bytes into string representing each byte as
* a two digit unsigned hex number.
*
* @param hash Array of bytes to convert to hex-string
* @return Generated hex string
*
* @since ostermillerutils 1.00.00
*/
private static String toHex(byte hash[]){
StringBuffer buf = new StringBuffer(hash.length * 2);
for (int i=0; i