1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.codec.digest;
19
20 import java.nio.charset.StandardCharsets;
21 import java.security.MessageDigest;
22 import java.security.NoSuchAlgorithmException;
23 import java.security.SecureRandom;
24 import java.util.Arrays;
25 import java.util.Objects;
26 import java.util.Random;
27 import java.util.regex.Matcher;
28 import java.util.regex.Pattern;
29
30 /**
31 * The libc crypt() "$1$" and Apache "$apr1$" MD5-based hash algorithm.
32 * <p>
33 * Based on the public domain ("beer-ware") C implementation from Poul-Henning Kamp which was found at:
34 * <a href="https://www.freebsd.org/cgi/cvsweb.cgi/src/lib/libcrypt/crypt-md5.c?rev=1.1;content-type=text%2Fplain"> crypt-md5.c @ freebsd.org</a>
35 * </p>
36 * <p>
37 * Source:
38 * </p>
39 *
40 * <pre>
41 * $FreeBSD: src/lib/libcrypt/crypt-md5.c,v 1.1 1999/01/21 13:50:09 brandon Exp $
42 * </pre>
43 * <p>
44 * Conversion to Kotlin and from there to Java in 2012.
45 * </p>
46 * <p>
47 * The C style comments are from the original C code, the ones with "//" from the port.
48 * </p>
49 * <p>
50 * This class is immutable and thread-safe.
51 * </p>
52 *
53 * @since 1.7
54 */
55 public class Md5Crypt {
56
57 /** The Identifier of the Apache variant. */
58 static final String APR1_PREFIX = "$apr1$";
59
60 /** The number of bytes of the final hash. */
61 private static final int BLOCKSIZE = 16;
62
63 /** The Identifier of this crypt() variant. */
64 static final String MD5_PREFIX = "$1$";
65
66 /** The number of rounds of the big loop. */
67 private static final int ROUNDS = 1000;
68
69 /**
70 * See {@link #apr1Crypt(byte[], String)} for details.
71 * <p>
72 * A salt is generated for you using {@link SecureRandom}; your own {@link Random} in {@link #apr1Crypt(byte[], Random)}.
73 * </p>
74 *
75 * @param keyBytes The plaintext bytes to hash. Each array element is set to {@code 0} before returning.
76 * @return The hash value.
77 * @throws IllegalArgumentException Thrown if a {@link NoSuchAlgorithmException} is caught.
78 * @see #apr1Crypt(byte[], String)
79 */
80 public static String apr1Crypt(final byte[] keyBytes) {
81 return apr1Crypt(keyBytes, APR1_PREFIX + B64.getRandomSalt(8));
82 }
83
84 /**
85 * See {@link #apr1Crypt(byte[], String)} for details.
86 * <p>
87 * A salt is generated for you using the user provided {@link Random}.
88 * </p>
89 *
90 * @param keyBytes The plaintext bytes to hash. Each array element is set to {@code 0} before returning.
91 * @param random The instance of {@link Random} to use for generating the salt. Consider using {@link SecureRandom} for more secure salts.
92 * @return The hash value.
93 * @throws IllegalArgumentException Thrown if a {@link NoSuchAlgorithmException} is caught.
94 * @see #apr1Crypt(byte[], String)
95 * @since 1.12
96 */
97 public static String apr1Crypt(final byte[] keyBytes, final Random random) {
98 return apr1Crypt(keyBytes, APR1_PREFIX + B64.getRandomSalt(8, random));
99 }
100
101 /**
102 * See {@link #apr1Crypt(String, String)} for details.
103 * <p>
104 * A salt is generated for you using {@link SecureRandom}
105 * </p>
106 *
107 * @param keyBytes The plaintext bytes to hash. Each array element is set to {@code 0} before returning.
108 * @param salt An APR1 salt. The salt may be null, in which case a salt is generated for you using {@link SecureRandom}.
109 * @return The hash value.
110 * @throws IllegalArgumentException Thrown if the salt does not match the allowed pattern.
111 * @throws IllegalArgumentException Thrown if a {@link NoSuchAlgorithmException} is caught.
112 */
113 public static String apr1Crypt(final byte[] keyBytes, String salt) {
114 // to make the md5Crypt regex happy
115 if (salt != null && !salt.startsWith(APR1_PREFIX)) {
116 salt = APR1_PREFIX + salt;
117 }
118 return md5Crypt(keyBytes, salt, APR1_PREFIX);
119 }
120
121 /**
122 * See {@link #apr1Crypt(String, String)} for details.
123 * <p>
124 * A salt is generated for you using {@link SecureRandom}.
125 * </p>
126 *
127 * @param keyBytes The plaintext string to hash. Each array element is set to {@code 0} before returning.
128 * @return The hash value.
129 * @throws IllegalArgumentException Thrown if a {@link NoSuchAlgorithmException} is caught.
130 * @see #apr1Crypt(byte[], String)
131 */
132 public static String apr1Crypt(final String keyBytes) {
133 return apr1Crypt(keyBytes.getBytes(StandardCharsets.UTF_8));
134 }
135
136 /**
137 * Generates an Apache htpasswd compatible "$apr1$" MD5 based hash value.
138 * <p>
139 * The algorithm is identical to the crypt(3) "$1$" one but produces different outputs due to the different salt prefix.
140 * </p>
141 *
142 * @param keyBytes The plaintext string to hash. Each array element is set to {@code 0} before returning.
143 * @param salt salt string including the prefix and optionally garbage at the end. The salt may be null, in which case a salt is generated for you using
144 * {@link SecureRandom}.
145 * @return The hash value.
146 * @throws IllegalArgumentException Thrown if the salt does not match the allowed pattern.
147 * @throws IllegalArgumentException Thrown if a {@link NoSuchAlgorithmException} is caught.
148 */
149 public static String apr1Crypt(final String keyBytes, final String salt) {
150 return apr1Crypt(keyBytes.getBytes(StandardCharsets.UTF_8), salt);
151 }
152
153 /**
154 * Generates a libc6 crypt() compatible "$1$" hash value.
155 * <p>
156 * See {@link #md5Crypt(byte[], String)} for details.
157 * </p>
158 * <p>
159 * A salt is generated for you using {@link SecureRandom}.
160 * </p>
161 *
162 * @param keyBytes The plaintext bytes to hash. Each array element is set to {@code 0} before returning.
163 * @return The hash value.
164 * @throws IllegalArgumentException Thrown if a {@link NoSuchAlgorithmException} is caught.
165 * @see #md5Crypt(byte[], String)
166 */
167 public static String md5Crypt(final byte[] keyBytes) {
168 return md5Crypt(keyBytes, MD5_PREFIX + B64.getRandomSalt(8));
169 }
170
171 /**
172 * Generates a libc6 crypt() compatible "$1$" hash value.
173 * <p>
174 * See {@link #md5Crypt(byte[], String)} for details.
175 * </p>
176 * <p>
177 * A salt is generated for you using the instance of {@link Random} you supply.
178 * </p>
179 *
180 * @param keyBytes The plaintext bytes to hash. Each array element is set to {@code 0} before returning.
181 * @param random The instance of {@link Random} to use for generating the salt. Consider using {@link SecureRandom} for more secure salts.
182 * @return The hash value.
183 * @throws IllegalArgumentException Thrown if a {@link NoSuchAlgorithmException} is caught.
184 * @see #md5Crypt(byte[], String)
185 * @since 1.12
186 */
187 public static String md5Crypt(final byte[] keyBytes, final Random random) {
188 return md5Crypt(keyBytes, MD5_PREFIX + B64.getRandomSalt(8, random));
189 }
190
191 /**
192 * Generates a libc crypt() compatible "$1$" MD5 based hash value.
193 * <p>
194 * See {@link Crypt#crypt(String, String)} for details. We use {@link SecureRandom} for seed generation by default.
195 * </p>
196 *
197 * @param keyBytes The plaintext bytes to hash. Each array element is set to {@code 0} before returning.
198 * @param salt salt string including the prefix and optionally garbage at the end. The salt may be null, in which case a salt is generated for you using
199 * {@link SecureRandom}.
200 * @return The hash value.
201 * @throws IllegalArgumentException Thrown if the salt does not match the allowed pattern.
202 * @throws IllegalArgumentException Thrown if a {@link NoSuchAlgorithmException} is caught.
203 */
204 public static String md5Crypt(final byte[] keyBytes, final String salt) {
205 return md5Crypt(keyBytes, salt, MD5_PREFIX);
206 }
207
208 /**
209 * Generates a libc6 crypt() "$1$" or Apache htpasswd "$apr1$" hash value.
210 * <p>
211 * See {@link Crypt#crypt(String, String)} or {@link #apr1Crypt(String, String)} for details. We use {@link SecureRandom by default}.
212 * </p>
213 *
214 * @param keyBytes The plaintext bytes to hash. Each array element is set to {@code 0} before returning.
215 * @param salt The real salt value without prefix or "rounds=". The salt may be null, in which case a salt is generated for you using {@link SecureRandom}.
216 * @param prefix The salt prefix {@value #APR1_PREFIX}, {@value #MD5_PREFIX}.
217 * @return The hash value.
218 * @throws IllegalArgumentException Thrown if the salt does not match the allowed pattern.
219 * @throws IllegalArgumentException Thrown if a {@link NoSuchAlgorithmException} is caught.
220 */
221 public static String md5Crypt(final byte[] keyBytes, final String salt, final String prefix) {
222 return md5Crypt(keyBytes, salt, prefix, new SecureRandom());
223 }
224
225 /**
226 * Generates a libc6 crypt() "$1$" or Apache htpasswd "$apr1$" hash value.
227 * <p>
228 * See {@link Crypt#crypt(String, String)} or {@link #apr1Crypt(String, String)} for details.
229 * </p>
230 *
231 * @param keyBytes The plaintext bytes to hash. Each array element is set to {@code 0} before returning.
232 * @param salt The real salt value without prefix or "rounds=". The salt may be null, in which case a salt is generated for you using {@link SecureRandom}.
233 * @param prefix The salt prefix {@value #APR1_PREFIX}, {@value #MD5_PREFIX}.
234 * @param random The instance of {@link Random} to use for generating the salt. Consider using {@link SecureRandom} for more secure salts.
235 * @return The hash value.
236 * @throws IllegalArgumentException Thrown if the salt or prefix does not match the allowed pattern.
237 * @throws IllegalArgumentException Thrown if a {@link NoSuchAlgorithmException} is caught.
238 * @since 1.12
239 */
240 public static String md5Crypt(final byte[] keyBytes, final String salt, final String prefix, final Random random) {
241 final int keyLen = keyBytes.length;
242 // Extract the real salt from the given string which can be a complete hash string.
243 final String saltString;
244 if (salt == null) {
245 saltString = B64.getRandomSalt(8, random);
246 } else {
247 Objects.requireNonNull(prefix, "prefix");
248 if (prefix.length() < 3 || prefix.charAt(0) != '$' && prefix.charAt(prefix.length() - 1) != '$') {
249 throw new IllegalArgumentException("Invalid prefix value: " + prefix);
250 }
251 final Pattern p = Pattern.compile("^" + prefix.replace("$", "\\$") + "([\\.\\/a-zA-Z0-9]{1,8}).*");
252 final Matcher m = p.matcher(salt);
253 if (!m.find()) {
254 throw new IllegalArgumentException("Invalid salt value: " + salt);
255 }
256 saltString = m.group(1);
257 }
258 final byte[] saltBytes = saltString.getBytes(StandardCharsets.UTF_8);
259 final MessageDigest messageDigestMd5 = DigestUtils.getMd5Digest();
260 /*
261 The password first, since that is what is most unknown
262 */
263 messageDigestMd5.update(keyBytes);
264 /*
265 * Then our magic string
266 */
267 messageDigestMd5.update(prefix.getBytes(StandardCharsets.UTF_8));
268 /*
269 * Then the raw salt
270 */
271 messageDigestMd5.update(saltBytes);
272 /*
273 * Then just as many characters of the MD5(pw,salt,pw)
274 */
275 MessageDigest altMessageDigestMd5 = DigestUtils.getMd5Digest();
276 altMessageDigestMd5.update(keyBytes);
277 altMessageDigestMd5.update(saltBytes);
278 altMessageDigestMd5.update(keyBytes);
279 byte[] finalb = altMessageDigestMd5.digest();
280 int ii = keyLen;
281 while (ii > 0) {
282 messageDigestMd5.update(finalb, 0, Math.min(ii, 16));
283 ii -= 16;
284 }
285 /*
286 * Don't leave anything around in JVM they could use.
287 */
288 Arrays.fill(finalb, (byte) 0);
289 /*
290 * Then something really weird...
291 */
292 ii = keyLen;
293 final int j = 0;
294 while (ii > 0) {
295 if ((ii & 1) == 1) {
296 messageDigestMd5.update(finalb[j]);
297 } else {
298 messageDigestMd5.update(keyBytes[j]);
299 }
300 ii >>= 1;
301 }
302 /*
303 * Now make the output string
304 */
305 final StringBuilder passwd = new StringBuilder(prefix + saltString + "$");
306 finalb = messageDigestMd5.digest();
307 /*
308 * and now, just to make sure things don't run too fast On a 60 Mhz Pentium this takes 34 milliseconds, so you would need 30 seconds to build a 1000
309 * entry dictionary...
310 */
311 for (int i = 0; i < ROUNDS; i++) {
312 altMessageDigestMd5 = DigestUtils.getMd5Digest();
313 if ((i & 1) != 0) {
314 altMessageDigestMd5.update(keyBytes);
315 } else {
316 altMessageDigestMd5.update(finalb, 0, BLOCKSIZE);
317 }
318 if (i % 3 != 0) {
319 altMessageDigestMd5.update(saltBytes);
320 }
321 if (i % 7 != 0) {
322 altMessageDigestMd5.update(keyBytes);
323 }
324 if ((i & 1) != 0) {
325 altMessageDigestMd5.update(finalb, 0, BLOCKSIZE);
326 } else {
327 altMessageDigestMd5.update(keyBytes);
328 }
329 finalb = altMessageDigestMd5.digest();
330 }
331 // The following was nearly identical to the Sha2Crypt code.
332 // Again, the buflen is not really needed.
333 // int buflen = MD5_PREFIX.length() - 1 + salt_string.length() + 1 + BLOCKSIZE + 1;
334 B64.b64from24bit(finalb[0], finalb[6], finalb[12], 4, passwd);
335 B64.b64from24bit(finalb[1], finalb[7], finalb[13], 4, passwd);
336 B64.b64from24bit(finalb[2], finalb[8], finalb[14], 4, passwd);
337 B64.b64from24bit(finalb[3], finalb[9], finalb[15], 4, passwd);
338 B64.b64from24bit(finalb[4], finalb[10], finalb[5], 4, passwd);
339 B64.b64from24bit((byte) 0, (byte) 0, finalb[11], 2, passwd);
340 /*
341 * Don't leave anything around in JVM they could use.
342 */
343 // Is there a better way to do this with the JVM?
344 messageDigestMd5.reset();
345 altMessageDigestMd5.reset();
346 Arrays.fill(keyBytes, (byte) 0);
347 Arrays.fill(saltBytes, (byte) 0);
348 Arrays.fill(finalb, (byte) 0);
349 return passwd.toString();
350 }
351
352 /**
353 * TODO Make private in 2.0.
354 *
355 * @deprecated TODO Make private in 2.0.
356 */
357 @Deprecated
358 public Md5Crypt() {
359 // empty
360 }
361 }