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