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}