001package co.codewizards.cloudstore.core.util;
002
003import co.codewizards.cloudstore.core.io.ByteArrayInputStream;
004import java.io.IOException;
005import java.io.InputStream;
006import java.io.UnsupportedEncodingException;
007import java.security.MessageDigest;
008import java.security.NoSuchAlgorithmException;
009import java.util.Locale;
010
011import co.codewizards.cloudstore.core.progress.NullProgressMonitor;
012import co.codewizards.cloudstore.core.progress.ProgressMonitor;
013
014public final class HashUtil {
015
016        /**
017         * Specifies usage of the MD5 algorithm in {@link #hash(String, InputStream)}.
018         */
019        public static final String HASH_ALGORITHM_MD5 = "MD5";
020        /**
021         * Specifies usage of the SHA algorithm in {@link #hash(String, InputStream)}.
022         */
023        public static final String HASH_ALGORITHM_SHA = "SHA";
024
025        private HashUtil() { }
026
027        public static String encodeHexStr(final byte[] buf)
028        {
029                return encodeHexStr(buf, 0, buf.length);
030        }
031
032        /**
033         * Encode a byte array into a human readable hex string. For each byte,
034         * two hex digits are produced. They are concatenated without any separators.
035         *
036         * @param buf The byte array to translate into human readable text.
037         * @param pos The start position (0-based).
038         * @param len The number of bytes that shall be processed beginning at the position specified by <code>pos</code>.
039         * @return a human readable string like "fa3d70" for a byte array with 3 bytes and these values.
040         * @see #encodeHexStr(byte[])
041         * @see #decodeHexStr(String)
042         */
043        public static String encodeHexStr(final byte[] buf, int pos, int len)
044        {
045                 final StringBuilder hex = new StringBuilder();
046                 while (len-- > 0) {
047                                final byte ch = buf[pos++];
048                                int d = (ch >> 4) & 0xf;
049                                hex.append((char)(d >= 10 ? 'a' - 10 + d : '0' + d));
050                                d = ch & 0xf;
051                                hex.append((char)(d >= 10 ? 'a' - 10 + d : '0' + d));
052                 }
053                 return hex.toString();
054        }
055
056        /**
057         * Decode a string containing two hex digits for each byte.
058         * @param hex The hex encoded string
059         * @return The byte array represented by the given hex string
060         * @see #encodeHexStr(byte[])
061         * @see #encodeHexStr(byte[], int, int)
062         */
063        public static byte[] decodeHexStr(final String hex)
064        {
065                if (hex.length() % 2 != 0)
066                        throw new IllegalArgumentException("The hex string must have an even number of characters!");
067
068                final byte[] res = new byte[hex.length() / 2];
069
070                int m = 0;
071                for (int i = 0; i < hex.length(); i += 2) {
072                        res[m++] = (byte) Integer.parseInt(hex.substring(i, i + 2), 16);
073                }
074
075                return res;
076        }
077
078        public static byte[] hash(final String algorithm, final InputStream in) throws NoSuchAlgorithmException, IOException {
079                return hash(algorithm, in, new NullProgressMonitor());
080        }
081
082        public static byte[] hash(final String algorithm, final InputStream in, final ProgressMonitor monitor) throws NoSuchAlgorithmException, IOException {
083                monitor.beginTask(algorithm, Math.max(1, in.available()));
084                try {
085                        final MessageDigest md = MessageDigest.getInstance(algorithm);
086                        final byte[] data = new byte[10240];
087                        while (true) {
088                                final int bytesRead = in.read(data, 0, data.length);
089                                if (bytesRead < 0) {
090                                        break;
091                                }
092                                if (bytesRead > 0) {
093                                        md.update(data, 0, bytesRead);
094                                        monitor.worked(bytesRead);
095                                }
096                        }
097                        return md.digest();
098                } finally {
099                        monitor.done();
100                }
101        }
102
103        public static String formatEncodedHexStrForHuman(String hex) {
104                if (hex.length() % 2 != 0)
105                        throw new IllegalArgumentException("The hex string must have an even number of characters!");
106
107                hex = hex.toUpperCase(Locale.UK);
108
109                final StringBuilder sb = new StringBuilder(hex.length() * 3 / 2);
110                for (int i = 0; i < hex.length(); i += 2) {
111                        if (sb.length() > 0)
112                                sb.append(':');
113
114                        sb.append(hex.substring(i, i + 2));
115                }
116                return sb.toString();
117        }
118
119        public static String sha1ForHuman(final byte[] in) {
120                try {
121                        return sha1ForHuman(new ByteArrayInputStream(in));
122                } catch (final IOException x) {
123                        throw new RuntimeException(x);
124                }
125        }
126
127        public static String sha1ForHuman(final InputStream in) throws IOException {
128                return formatEncodedHexStrForHuman(sha1(in));
129        }
130
131        public static String sha1(final String in) {
132                try {
133                        return sha1(in.getBytes(IOUtil.CHARSET_NAME_UTF_8));
134                } catch (final UnsupportedEncodingException e) {
135                        throw new RuntimeException(e);
136                }
137        }
138
139        public static String sha1(final byte[] in) {
140                try {
141                        return sha1(new ByteArrayInputStream(in));
142                } catch (final IOException x) {
143                        throw new RuntimeException(x);
144                }
145        }
146
147        public static String sha1(final InputStream in) throws IOException {
148                byte[] hash;
149                try {
150                        hash = hash(HASH_ALGORITHM_SHA, in);
151                } catch (final NoSuchAlgorithmException e) {
152                        throw new RuntimeException(e);
153                }
154                return encodeHexStr(hash);
155        }
156
157}