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}