001/*
002 * Copyright 2002-2020 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.util;
018
019import java.io.IOException;
020import java.io.InputStream;
021import java.security.MessageDigest;
022import java.security.NoSuchAlgorithmException;
023
024/**
025 * Miscellaneous methods for calculating digests.
026 *
027 * <p>Mainly for internal use within the framework; consider
028 * <a href="https://commons.apache.org/codec/">Apache Commons Codec</a>
029 * for a more comprehensive suite of digest utilities.
030 *
031 * @author Arjen Poutsma
032 * @author Juergen Hoeller
033 * @author Craig Andrews
034 * @since 3.0
035 */
036public abstract class DigestUtils {
037
038        private static final String MD5_ALGORITHM_NAME = "MD5";
039
040        private static final char[] HEX_CHARS =
041                        {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
042
043
044        /**
045         * Calculate the MD5 digest of the given bytes.
046         * @param bytes the bytes to calculate the digest over
047         * @return the digest
048         */
049        public static byte[] md5Digest(byte[] bytes) {
050                return digest(MD5_ALGORITHM_NAME, bytes);
051        }
052
053        /**
054         * Calculate the MD5 digest of the given stream.
055         * <p>This method does <strong>not</strong> close the input stream.
056         * @param inputStream the InputStream to calculate the digest over
057         * @return the digest
058         * @since 4.2
059         */
060        public static byte[] md5Digest(InputStream inputStream) throws IOException {
061                return digest(MD5_ALGORITHM_NAME, inputStream);
062        }
063
064        /**
065         * Return a hexadecimal string representation of the MD5 digest of the given bytes.
066         * @param bytes the bytes to calculate the digest over
067         * @return a hexadecimal digest string
068         */
069        public static String md5DigestAsHex(byte[] bytes) {
070                return digestAsHexString(MD5_ALGORITHM_NAME, bytes);
071        }
072
073        /**
074         * Return a hexadecimal string representation of the MD5 digest of the given stream.
075         * <p>This method does <strong>not</strong> close the input stream.
076         * @param inputStream the InputStream to calculate the digest over
077         * @return a hexadecimal digest string
078         * @since 4.2
079         */
080        public static String md5DigestAsHex(InputStream inputStream) throws IOException {
081                return digestAsHexString(MD5_ALGORITHM_NAME, inputStream);
082        }
083
084        /**
085         * Append a hexadecimal string representation of the MD5 digest of the given
086         * bytes to the given {@link StringBuilder}.
087         * @param bytes the bytes to calculate the digest over
088         * @param builder the string builder to append the digest to
089         * @return the given string builder
090         */
091        public static StringBuilder appendMd5DigestAsHex(byte[] bytes, StringBuilder builder) {
092                return appendDigestAsHex(MD5_ALGORITHM_NAME, bytes, builder);
093        }
094
095        /**
096         * Append a hexadecimal string representation of the MD5 digest of the given
097         * inputStream to the given {@link StringBuilder}.
098         * <p>This method does <strong>not</strong> close the input stream.
099         * @param inputStream the inputStream to calculate the digest over
100         * @param builder the string builder to append the digest to
101         * @return the given string builder
102         * @since 4.2
103         */
104        public static StringBuilder appendMd5DigestAsHex(InputStream inputStream, StringBuilder builder) throws IOException {
105                return appendDigestAsHex(MD5_ALGORITHM_NAME, inputStream, builder);
106        }
107
108
109        /**
110         * Create a new {@link MessageDigest} with the given algorithm.
111         * <p>Necessary because {@code MessageDigest} is not thread-safe.
112         */
113        private static MessageDigest getDigest(String algorithm) {
114                try {
115                        return MessageDigest.getInstance(algorithm);
116                }
117                catch (NoSuchAlgorithmException ex) {
118                        throw new IllegalStateException("Could not find MessageDigest with algorithm \"" + algorithm + "\"", ex);
119                }
120        }
121
122        private static byte[] digest(String algorithm, byte[] bytes) {
123                return getDigest(algorithm).digest(bytes);
124        }
125
126        private static byte[] digest(String algorithm, InputStream inputStream) throws IOException {
127                MessageDigest messageDigest = getDigest(algorithm);
128                if (inputStream instanceof UpdateMessageDigestInputStream){
129                        ((UpdateMessageDigestInputStream) inputStream).updateMessageDigest(messageDigest);
130                        return messageDigest.digest();
131                }
132                else {
133                        final byte[] buffer = new byte[StreamUtils.BUFFER_SIZE];
134                        int bytesRead = -1;
135                        while ((bytesRead = inputStream.read(buffer)) != -1) {
136                                messageDigest.update(buffer, 0, bytesRead);
137                        }
138                        return messageDigest.digest();
139                }
140        }
141
142        private static String digestAsHexString(String algorithm, byte[] bytes) {
143                char[] hexDigest = digestAsHexChars(algorithm, bytes);
144                return new String(hexDigest);
145        }
146
147        private static String digestAsHexString(String algorithm, InputStream inputStream) throws IOException {
148                char[] hexDigest = digestAsHexChars(algorithm, inputStream);
149                return new String(hexDigest);
150        }
151
152        private static StringBuilder appendDigestAsHex(String algorithm, byte[] bytes, StringBuilder builder) {
153                char[] hexDigest = digestAsHexChars(algorithm, bytes);
154                return builder.append(hexDigest);
155        }
156
157        private static StringBuilder appendDigestAsHex(String algorithm, InputStream inputStream, StringBuilder builder)
158                        throws IOException {
159
160                char[] hexDigest = digestAsHexChars(algorithm, inputStream);
161                return builder.append(hexDigest);
162        }
163
164        private static char[] digestAsHexChars(String algorithm, byte[] bytes) {
165                byte[] digest = digest(algorithm, bytes);
166                return encodeHex(digest);
167        }
168
169        private static char[] digestAsHexChars(String algorithm, InputStream inputStream) throws IOException {
170                byte[] digest = digest(algorithm, inputStream);
171                return encodeHex(digest);
172        }
173
174        private static char[] encodeHex(byte[] bytes) {
175                char[] chars = new char[32];
176                for (int i = 0; i < chars.length; i = i + 2) {
177                        byte b = bytes[i / 2];
178                        chars[i] = HEX_CHARS[(b >>> 0x4) & 0xf];
179                        chars[i + 1] = HEX_CHARS[b & 0xf];
180                }
181                return chars;
182        }
183
184}