001/*-------------------------------------------------------------------------+ 002| | 003| Copyright 2005-2011 The ConQAT Project | 004| | 005| Licensed under the Apache License, Version 2.0 (the "License"); | 006| you may not use this file except in compliance with the License. | 007| You may obtain a copy of the License at | 008| | 009| http://www.apache.org/licenses/LICENSE-2.0 | 010| | 011| Unless required by applicable law or agreed to in writing, software | 012| distributed under the License is distributed on an "AS IS" BASIS, | 013| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 014| See the License for the specific language governing permissions and | 015| limitations under the License. | 016+-------------------------------------------------------------------------*/ 017package org.conqat.lib.commons.digest; 018 019import java.security.MessageDigest; 020import java.security.NoSuchAlgorithmException; 021import java.util.Collection; 022import java.util.List; 023 024import org.conqat.lib.commons.collections.CollectionUtils; 025import org.conqat.lib.commons.error.EnvironmentError; 026import org.conqat.lib.commons.string.StringUtils; 027 028/** 029 * Utility functions for creation of digests. 030 */ 031public class Digester { 032 033 /** 034 * MD5 Digesters used organized by thread. This is used to avoid recreation of 035 * digesters, while keeping the code thread safe (i.e. each thread has its own 036 * instance). 037 */ 038 private static ThreadLocal<MessageDigest> md5Digesters = new ThreadLocal<MessageDigest>() { 039 /** {@inheritDoc} */ 040 @Override 041 protected MessageDigest initialValue() { 042 return getMD5(); 043 } 044 }; 045 046 /** 047 * SHA-1 Digesters used organized by thread. This is used to avoid recreation of 048 * digesters, while keeping the code thread safe (i.e. each thread has its own 049 * instance). 050 */ 051 private static ThreadLocal<MessageDigest> sha1Digesters = new ThreadLocal<MessageDigest>() { 052 /** {@inheritDoc} */ 053 @Override 054 protected MessageDigest initialValue() { 055 return getSHA1(); 056 } 057 }; 058 059 /** 060 * SHA-256 Digesters used organized by thread. This is used to avoid recreation 061 * of digesters, while keeping the code thread safe (i.e. each thread has its 062 * own instance). 063 */ 064 private static ThreadLocal<MessageDigest> sha256Digesters = new ThreadLocal<MessageDigest>() { 065 /** {@inheritDoc} */ 066 @Override 067 protected MessageDigest initialValue() { 068 return getSHA256(); 069 } 070 }; 071 072 /** 073 * Computes an MD5 hash for a string. The hash is always 32 characters long and 074 * only uses characters from [0-9A-F]. 075 */ 076 public static String createMD5Digest(String base) { 077 return createMD5Digest(base.getBytes()); 078 } 079 080 /** 081 * Computes an MD5 hash for a byte array. The hash is always 32 characters long 082 * and only uses characters from [0-9A-F]. 083 */ 084 public static String createMD5Digest(byte[] data) { 085 MessageDigest digester = md5Digesters.get(); 086 digester.reset(); 087 return StringUtils.encodeAsHex(digester.digest(data)); 088 } 089 090 /** 091 * Computes a SHA-1 hash for a string. The hash is always 40 characters long and 092 * only uses characters from [0-9A-F]. 093 */ 094 public static String createSHA1Digest(String base) { 095 return createSHA1Digest(base.getBytes()); 096 } 097 098 /** 099 * Computes a SHA-1 hash for a string. The hash is always 40 characters long and 100 * only uses characters from [0-9A-F]. 101 */ 102 public static String createSHA1Digest(byte[] data) { 103 MessageDigest digester = sha1Digesters.get(); 104 digester.reset(); 105 return StringUtils.encodeAsHex(digester.digest(data)); 106 } 107 108 /** 109 * Computes a SHA-256 hash for a string. The hash is always 64 characters long 110 * and only uses characters from [0-9A-F]. 111 */ 112 public static String createSHA256Digest(String base) { 113 return createSHA256Digest(base.getBytes()); 114 } 115 116 /** 117 * Computes a SHA-256 hash for a string. The hash is always 64 characters long 118 * and only uses characters from [0-9A-F]. 119 */ 120 public static String createSHA256Digest(byte[] data) { 121 MessageDigest digester = sha256Digesters.get(); 122 digester.reset(); 123 return StringUtils.encodeAsHex(digester.digest(data)); 124 } 125 126 /** 127 * Computes an MD5 hash for a collection of strings. The strings are sorted 128 * before MD5 computation, so that the resulting MD5 hash is independent of the 129 * order of the strings in the collection. 130 */ 131 public static String createMD5Digest(Collection<String> bases) { 132 List<String> sortedBases = CollectionUtils.sort(bases); 133 return createMD5Digest(StringUtils.concat(sortedBases, StringUtils.EMPTY_STRING)); 134 } 135 136 /** 137 * Computes an SHA-1 hash for a byte array and returns the binary hash (i.e. no 138 * string conversion). 139 */ 140 public static byte[] createBinarySHA1Digest(byte[] data) { 141 MessageDigest digester = sha1Digesters.get(); 142 digester.reset(); 143 return digester.digest(data); 144 } 145 146 /** 147 * Computes an MD5 hash for a byte array and returns the binary hash (i.e. no 148 * string conversion). 149 */ 150 public static byte[] createBinaryMD5Digest(byte[] data) { 151 MessageDigest digester = md5Digesters.get(); 152 digester.reset(); 153 return digester.digest(data); 154 } 155 156 /** 157 * Returns MD5 digester or throws an AssertionError if the digester could not be 158 * located. 159 */ 160 public static MessageDigest getMD5() { 161 return getDigester("MD5"); 162 } 163 164 /** 165 * Returns SHA-1 digester or throws an AssertionError if the digester could not 166 * be located. 167 */ 168 public static MessageDigest getSHA1() { 169 return getDigester("SHA-1"); 170 } 171 172 /** 173 * Returns SHA-256 digester or throws an AssertionError if the digester could 174 * not be located. 175 */ 176 public static MessageDigest getSHA256() { 177 return getDigester("SHA-256"); 178 } 179 180 /** 181 * Returns a digester or throws an AssertionError if the digester could not be 182 * located. 183 */ 184 private static MessageDigest getDigester(String algorithmName) { 185 try { 186 return MessageDigest.getInstance(algorithmName); 187 } catch (NoSuchAlgorithmException e) { 188 throw new EnvironmentError("No " + algorithmName + " algorithm found. Please check your JRE installation", 189 e); 190 } 191 } 192 193}