/* * RHQ Management Platform * Copyright (C) 2005-2010 Red Hat, Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, as * published by the Free Software Foundation, and/or the GNU Lesser * General Public License, version 2.1, also as published by the Free * Software Foundation. * * 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 and the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU General Public License * and the GNU Lesser General Public License along with this program; * if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.rhq.core.util; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; /** * An object that generates a message digest or hash for algorithms such as MD5 or SHA. This class is basically a * wrapper around {@link java.security.MessageDigest} and provides convenience methods making it easier to generate * hashes. * * There are static methods in here that use MD5 as the default algorithm. If you want to use another * algorithm, instantiate an instance of this object and use its instance methods. */ public class MessageDigestGenerator { /** you can pass this to the constructor to indicate you want to generate MD5 message digests */ public static final String MD5 = "MD5"; /** you can pass this to the constructor to indicate you want to generate SHA 256 message digests */ public static final String SHA_256 = "SHA-256"; private final MessageDigest messageDigest; /** * Creates a new {@link MessageDigestGenerator} object using MD5 as the default algorithm. * <p/> * MD5 is used as the default algorithm for backward compatibility. It originally only supported MD5 and has since * been refactored to support algortithms that are supported by your version of Java. * * @throws IllegalStateException if the MD5 algorithm cannot be computed by the VM */ public MessageDigestGenerator() { this(MD5); } /** * Creates a new MessageDigestGenerator using the specified algorithm. * * @param algorithm The algorithm to use (e.g., MD5, SHA-256) * * @throws IllegalStateException if the algorithm is not supported by the VM */ public MessageDigestGenerator(String algorithm) { try { messageDigest = MessageDigest.getInstance(algorithm); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException(algorithm + " is not a supported algorithm"); } } /** * Returns the <code>MessageDigest</code> object that is used to compute the digest. * * @return object that will perform the calculations */ public MessageDigest getMessageDigest() { return this.messageDigest; } /** * Use this to add more data to the set of data used to calculate the digest. Once all data has been added, call * {@link #getDigest()} to get the final value. * * @param is the stream whose data is to be part of the set of data from which the digest is to be calculated * * @throws IOException if there was a problem reading from the stream */ public void add(InputStream is) throws IOException { byte[] bytes = new byte[1024]; int len; while ((len = is.read(bytes, 0, bytes.length)) != -1) { messageDigest.update(bytes, 0, len); } return; } /** * Use this to add more data to the set of data used to calculate the hash. Once all data has been added, call * {@link #getDigest()} to get the final digest value. * * <p>If <code>bytes</code> is <code>null</code>, this method is a no-op and simply returns.</p> * * @param bytes data to be part of the set of data from which the digest is to be calculated */ public void add(byte[] bytes) { if (bytes != null) { messageDigest.update(bytes); } } /** * Use this to add more data to the set of data used to calculate the hash. Once all data has been added, call * {@link #getDigest()} to get the final digest value. * * <p>If <code>bytes</code> is <code>null</code>, this method is a no-op and simply returns.</p> * * @param bytes data to be part of the set of data from which the digest is to be calculated * @param offset the offset to start from in the array of bytes * @param length the number of bytes to use, starting at offset */ public void add(byte[] bytes, int offset, int length) { if (bytes != null) { messageDigest.update(bytes, offset, length); } } /** * After all the data has been added to the message digest via add methods, this method is used to * finalize the digest calcualation and return the digest. You can get the String form of this digest if * you call {@link #getDigestString()} instead. * * @return the bytes of the digest */ public byte[] getDigest() { return this.messageDigest.digest(); } /** * After all the data has been added to the message digest via add methods, * this method is used to finalize the digest calcualation and return the digest as a String. You can get the * actual bytes of the digest if you call {@link #getDigest()} instead. * * @return the digest as a string */ public String getDigestString() { return calculateDigestStringFromBytes(getDigest()); } /** * Returns the digest for the data found in the given stream. The digest is returned as a byte array; if you want * the digest as a String, call getDigestString methods instead. * * @param is the stream whose data is to be used to calculate the digest * * @return the stream data's hash * * @throws IOException if failed to read the stream for some reason */ public static byte[] getDigest(InputStream is) throws IOException { MessageDigestGenerator md5 = new MessageDigestGenerator(MD5); return md5.calcDigest(is); } public byte[] calcDigest(InputStream is) throws IOException { add(is); return getDigest(); } /** * Similar to {@link #getDigest(InputStream)}, only this returns the digest as a String. * * @param is the stream whose data is to be used to calculate the digest * * @return the stream data's digest as a String * * @throws IOException if failed to read the stream for some reason */ public static String getDigestString(InputStream is) throws IOException { MessageDigestGenerator md5 = new MessageDigestGenerator(MD5); return md5.calcDigestString(is); } public String calcDigestString(InputStream is) throws IOException { add(is); return getDigestString(); } /** * Calculates a digest for a given string. * * @param source_str the string whose contents will be used as the data to calculate the digest * * @return the string's digest * * @throws RuntimeException if a system error occurred - should never really happen */ public static byte[] getDigest(String source_str) { MessageDigestGenerator md5 = new MessageDigestGenerator(MD5); return md5.calcDigest(source_str); } public byte[] calcDigest(String source_str) { try { ByteArrayInputStream bs = new ByteArrayInputStream(source_str.getBytes()); return calcDigest(bs); } catch (IOException e) { throw new RuntimeException("IOException reading a byte array input stream, this should never happen", e); } } /** * Calculates a digest for a given string and returns the digest's String representation. * * @param source_str the string whose contents will be used as the data to calculate the digest * * @return the string's digest or hash as a String */ public static String getDigestString(String source_str) { MessageDigestGenerator md5 = new MessageDigestGenerator(MD5); return md5.calcDigestString(source_str); } public String calcDigestString(String source_str) { return calculateDigestStringFromBytes(calcDigest(source_str)); } /** * Calculates the digest for a given file. The file's contents will be used as the source data for the digest calculation. * * @param file the file whose contents are to be used to calculate the digest. * * @return the file content's digest * * @throws IOException if the file could not be read or accessed */ public static byte[] getDigest(File file) throws IOException { MessageDigestGenerator md5 = new MessageDigestGenerator(MD5); return md5.calcDigest(file); } public byte[] calcDigest(File file) throws IOException { FileInputStream is = null; try { is = new FileInputStream(file); return calcDigest(new BufferedInputStream(is, 1024 * 32)); } finally { if (is != null) { is.close(); } } } public byte[] calcDigest(URL url) throws IOException { InputStream is = null; try { is = url.openStream(); return calcDigest(new BufferedInputStream(is, 1024 * 32)); } finally { if (is != null) { is.close(); } } } /** * Calculates the digest for a given file. The file's contents will be used as the source data for the digest calculation. * * @param file the file whose contents are to be used to calculate the digest. * * @return the file content's digest as a String * * @throws IOException if the file could not be read or accessed */ public static String getDigestString(File file) throws IOException { MessageDigestGenerator md5 = new MessageDigestGenerator(MD5); return md5.calcDigestString(file); } public String calcDigestString(File file) throws IOException { return calculateDigestStringFromBytes(calcDigest(file)); } public String calcDigestString(byte[] bytes) throws IOException { try { ByteArrayInputStream bs = new ByteArrayInputStream(bytes); return calcDigestString(bs); } catch (IOException e) { throw new RuntimeException("IOException reading a byte array input stream, this should never happen", e); } } /** * Calculates the digest for a given file. The file's contents will be used as the source data for the digest calculation. * * @param url the URL whose contents are to be used to calculate the digest. * * @return the URL content's digest as a String * * @throws IOException if the URL could not be read or accessed */ public static String getDigestString(URL url) throws IOException { MessageDigestGenerator md5 = new MessageDigestGenerator(MD5); return md5.calcDigestString(url); } public String calcDigestString(URL url) throws IOException { return calculateDigestStringFromBytes(calcDigest(url)); } /** * Given a digest byte array, this will return its String representation. * * @param bytes the digest whose String representation is to be returned * * @return the digest string */ private static String calculateDigestStringFromBytes(byte[] bytes) { StringBuffer sb = new StringBuffer(bytes.length * 2); for (int i = 0; i < bytes.length; i++) { int hi = (bytes[i] >> 4) & 0xf; int lo = bytes[i] & 0xf; sb.append(Character.forDigit(hi, 16)); sb.append(Character.forDigit(lo, 16)); } return sb.toString(); } /** * This can be used to generate the digest hash from the command line. * * @param args one and only one filename - may or may not be a .jar file. * * @throws Exception if failed to compute the digest for some reason */ public static void main(String[] args) throws Exception { String file = args[0]; String digest = MessageDigestGenerator.getDigestString(new File(file)); System.out.println("MD5=" + digest); digest = new MessageDigestGenerator(SHA_256).calcDigestString(new File(file)); System.out.println("SHA-256=" + digest); } }