001package test; 002 003import com.killcoding.log.Logger; 004import com.killcoding.log.LoggerFactory; 005 006import java.io.File; 007import java.io.IOException; 008import java.io.InputStream; 009import java.nio.charset.StandardCharsets; 010import java.nio.file.Files; 011import java.security.MessageDigest; 012import java.security.NoSuchAlgorithmException; 013import java.util.Map; 014import java.util.concurrent.ConcurrentHashMap; 015 016/** 017 * Includes methods to generate the MD5 and SHA1 checksum. 018 * 019 * @author Jeremy Long 020 * @version $Id: $Id 021 */ 022public final class Checksum { 023 024 /** 025 * Hex code characters used in getHex. 026 */ 027 private static final String HEXES = "0123456789abcdef"; 028 /** 029 * Buffer size for calculating checksums. 030 */ 031 private static final int BUFFER_SIZE = 1024; 032 033 /** 034 * The logger. 035 */ 036 private static final Logger LOGGER = LoggerFactory.getLogger(Checksum.class); 037 /** 038 * MD5 constant. 039 */ 040 private static final String MD5 = "MD5"; 041 /** 042 * SHA1 constant. 043 */ 044 private static final String SHA1 = "SHA-1"; 045 /** 046 * SHA256 constant. 047 */ 048 private static final String SHA256 = "SHA-256"; 049 /** 050 * Cached file checksums for each supported algorithm. 051 */ 052 private static final Map<File, FileChecksums> CHECKSUM_CACHE = new ConcurrentHashMap<>(); 053 054 /** 055 * Private constructor for a utility class. 056 */ 057 private Checksum() { 058 super(); 059 } 060 061 /** 062 * <p> 063 * Creates the cryptographic checksum of a given file using the specified 064 * algorithm.</p> 065 * 066 * @param algorithm the algorithm to use to calculate the checksum 067 * @param file the file to calculate the checksum for 068 * @return the checksum 069 * @throws java.io.IOException when the file does not exist 070 * @throws java.security.NoSuchAlgorithmException when an algorithm is 071 * specified that does not exist 072 */ 073 public static String getChecksum(String algorithm, File file) throws NoSuchAlgorithmException, IOException { 074 FileChecksums fileChecksums = CHECKSUM_CACHE.get(file); 075 if (fileChecksums == null) { 076 try (InputStream stream = Files.newInputStream(file.toPath())) { 077 final MessageDigest md5Digest = getMessageDigest(MD5); 078 final MessageDigest sha1Digest = getMessageDigest(SHA1); 079 final MessageDigest sha256Digest = getMessageDigest(SHA256); 080 final byte[] buffer = new byte[BUFFER_SIZE]; 081 int read = stream.read(buffer, 0, BUFFER_SIZE); 082 while (read > -1) { 083 // update all checksums together instead of reading the file multiple times 084 md5Digest.update(buffer, 0, read); 085 sha1Digest.update(buffer, 0, read); 086 sha256Digest.update(buffer, 0, read); 087 read = stream.read(buffer, 0, BUFFER_SIZE); 088 } 089 fileChecksums = new FileChecksums( 090 getHex(md5Digest.digest()), 091 getHex(sha1Digest.digest()), 092 getHex(sha256Digest.digest()) 093 ); 094 CHECKSUM_CACHE.put(file, fileChecksums); 095 } 096 } 097 switch (algorithm.toUpperCase()) { 098 case MD5: 099 return fileChecksums.md5; 100 case SHA1: 101 return fileChecksums.sha1; 102 case SHA256: 103 return fileChecksums.sha256; 104 default: 105 throw new NoSuchAlgorithmException(algorithm); 106 } 107 } 108 109 /** 110 * Calculates the MD5 checksum of a specified file. 111 * 112 * @param file the file to generate the MD5 checksum 113 * @return the hex representation of the MD5 hash 114 * @throws java.io.IOException when the file passed in does not exist 115 * @throws java.security.NoSuchAlgorithmException when the MD5 algorithm is 116 * not available 117 */ 118 public static String getMD5Checksum(File file) throws IOException, NoSuchAlgorithmException { 119 return getChecksum(MD5, file); 120 } 121 122 /** 123 * Calculates the SHA1 checksum of a specified file. 124 * 125 * @param file the file to generate the MD5 checksum 126 * @return the hex representation of the SHA1 hash 127 * @throws java.io.IOException when the file passed in does not exist 128 * @throws java.security.NoSuchAlgorithmException when the SHA1 algorithm is 129 * not available 130 */ 131 public static String getSHA1Checksum(File file) throws IOException, NoSuchAlgorithmException { 132 return getChecksum(SHA1, file); 133 } 134 135 /** 136 * Calculates the SH256 checksum of a specified file. 137 * 138 * @param file the file to generate the MD5 checksum 139 * @return the hex representation of the SHA1 hash 140 * @throws java.io.IOException when the file passed in does not exist 141 * @throws java.security.NoSuchAlgorithmException when the SHA1 algorithm is 142 * not available 143 */ 144 public static String getSHA256Checksum(File file) throws IOException, NoSuchAlgorithmException { 145 return getChecksum(SHA256, file); 146 } 147 148 /** 149 * Calculates the MD5 checksum of a specified bytes. 150 * 151 * @param algorithm the algorithm to use (md5, sha1, etc.) to calculate the 152 * message digest 153 * @param bytes the bytes to generate the MD5 checksum 154 * @return the hex representation of the MD5 hash 155 */ 156 public static String getChecksum(String algorithm, byte[] bytes) { 157 return getHex(getMessageDigest(algorithm).digest(bytes)); 158 } 159 160 /** 161 * Calculates the MD5 checksum of the specified text. 162 * 163 * @param text the text to generate the MD5 checksum 164 * @return the hex representation of the MD5 165 */ 166 public static String getMD5Checksum(String text) { 167 return getChecksum(MD5, stringToBytes(text)); 168 } 169 170 /** 171 * Calculates the SHA1 checksum of the specified text. 172 * 173 * @param text the text to generate the SHA1 checksum 174 * @return the hex representation of the SHA1 175 */ 176 public static String getSHA1Checksum(String text) { 177 return getChecksum(SHA1, stringToBytes(text)); 178 } 179 180 /** 181 * Calculates the SHA256 checksum of the specified text. 182 * 183 * @param text the text to generate the SHA1 checksum 184 * @return the hex representation of the SHA1 185 */ 186 public static String getSHA256Checksum(String text) { 187 return getChecksum(SHA256, stringToBytes(text)); 188 } 189 190 /** 191 * Converts the given text into bytes. 192 * 193 * @param text the text to convert 194 * @return the bytes 195 */ 196 private static byte[] stringToBytes(String text) { 197 return text.getBytes(StandardCharsets.UTF_8); 198 } 199 200 /** 201 * <p> 202 * Converts a byte array into a hex string.</p> 203 * 204 * <p> 205 * This method was copied from <a 206 * href="http://www.rgagnon.com/javadetails/java-0596.html">http://www.rgagnon.com/javadetails/java-0596.html</a></p> 207 * 208 * @param raw a byte array 209 * @return the hex representation of the byte array 210 */ 211 public static String getHex(byte[] raw) { 212 if (raw == null) { 213 return null; 214 } 215 final StringBuilder hex = new StringBuilder(2 * raw.length); 216 for (final byte b : raw) { 217 hex.append(HEXES.charAt((b & 0xF0) >> 4)).append(HEXES.charAt(b & 0x0F)); 218 } 219 return hex.toString(); 220 } 221 222 /** 223 * Returns the message digest. 224 * 225 * @param algorithm the algorithm for the message digest 226 * @return the message digest 227 */ 228 private static MessageDigest getMessageDigest(String algorithm) { 229 try { 230 return MessageDigest.getInstance(algorithm); 231 } catch (NoSuchAlgorithmException e) { 232 LOGGER.error(e.getMessage(), e); 233 final String msg = String.format("Failed to obtain the %s message digest.", algorithm); 234 throw new IllegalStateException(msg, e); 235 } 236 } 237 238 /** 239 * File checksums for each supported algorithm 240 */ 241 private static class FileChecksums { 242 243 /** 244 * MD5. 245 */ 246 private final String md5; 247 /** 248 * SHA1. 249 */ 250 private final String sha1; 251 /** 252 * SHA256. 253 */ 254 private final String sha256; 255 256 FileChecksums(String md5, String sha1, String sha256) { 257 this.md5 = md5; 258 this.sha1 = sha1; 259 this.sha256 = sha256; 260 } 261 } 262}