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="https://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 *
174 * @param keyBytes
175 * plaintext string to hash. Each array element is set to {@code 0} before returning.
176 * @return the hash value.
177 * @throws IllegalArgumentException
178 * when a {@link java.security.NoSuchAlgorithmException} is caught.
179 * @see #md5Crypt(byte[], String)
180 */
181 public static String md5Crypt(final byte[] keyBytes) {
182 return md5Crypt(keyBytes, MD5_PREFIX + B64.getRandomSalt(8));
183 }
184
185 /**
186 * Generates a libc6 crypt() compatible "$1$" hash value.
187 * <p>
188 * See {@link #md5Crypt(byte[], String)} for details.
189 * </p>
190 * <p>
191 * A salt is generated for you using the instance of {@link Random} you supply.
192 * </p>
193 *
194 * @param keyBytes
195 * plaintext string to hash. Each array element is set to {@code 0} before returning.
196 * @param random
197 * the instance of {@link Random} to use for generating the salt.
198 * Consider using {@link SecureRandom} for more secure salts.
199 * @return the hash value.
200 * @throws IllegalArgumentException
201 * when a {@link java.security.NoSuchAlgorithmException} is caught.
202 * @see #md5Crypt(byte[], String)
203 * @since 1.12
204 */
205 public static String md5Crypt(final byte[] keyBytes, final Random random) {
206 return md5Crypt(keyBytes, MD5_PREFIX + B64.getRandomSalt(8, random));
207 }
208
209 /**
210 * Generates a libc crypt() compatible "$1$" MD5 based hash value.
211 * <p>
212 * See {@link Crypt#crypt(String, String)} for details. We use {@link SecureRandom} for seed generation by
213 * default.
214 * </p>
215 *
216 * @param keyBytes
217 * plaintext string to hash. Each array element is set to {@code 0} before returning.
218 * @param salt
219 * salt string including the prefix and optionally garbage at the end. The salt may be null, in which
220 * case a salt is generated for you using {@link SecureRandom}.
221 * @return the hash value.
222 * @throws IllegalArgumentException
223 * if the salt does not match the allowed pattern.
224 * @throws IllegalArgumentException
225 * when a {@link java.security.NoSuchAlgorithmException} is caught.
226 */
227 public static String md5Crypt(final byte[] keyBytes, final String salt) {
228 return md5Crypt(keyBytes, salt, MD5_PREFIX);
229 }
230
231 /**
232 * Generates a libc6 crypt() "$1$" or Apache htpasswd "$apr1$" hash value.
233 * <p>
234 * See {@link Crypt#crypt(String, String)} or {@link #apr1Crypt(String, String)} for details. We use
235 * {@link SecureRandom by default}.
236 * </p>
237 *
238 * @param keyBytes
239 * plaintext string to hash. Each array element is set to {@code 0} before returning.
240 * @param salt
241 * real salt value without prefix or "rounds=". The salt may be null, in which case a salt
242 * is generated for you using {@link SecureRandom}.
243 * @param prefix
244 * The salt prefix {@value #APR1_PREFIX}, {@value #MD5_PREFIX}.
245 * @return the hash value.
246 * @throws IllegalArgumentException
247 * if the salt does not match the allowed pattern.
248 * @throws IllegalArgumentException
249 * when a {@link java.security.NoSuchAlgorithmException} is caught.
250 */
251 public static String md5Crypt(final byte[] keyBytes, final String salt, final String prefix) {
252 return md5Crypt(keyBytes, salt, prefix, new SecureRandom());
253 }
254
255 /**
256 * Generates a libc6 crypt() "$1$" or Apache htpasswd "$apr1$" hash value.
257 * <p>
258 * See {@link Crypt#crypt(String, String)} or {@link #apr1Crypt(String, String)} for details.
259 * </p>
260 *
261 * @param keyBytes
262 * plaintext string to hash. Each array element is set to {@code 0} before returning.
263 * @param salt
264 * real salt value without prefix or "rounds=". The salt may be null, in which case a salt
265 * is generated for you using {@link SecureRandom}.
266 * @param prefix
267 * The salt prefix {@value #APR1_PREFIX}, {@value #MD5_PREFIX}.
268 * @param random
269 * the instance of {@link Random} to use for generating the salt.
270 * Consider using {@link SecureRandom} for more secure salts.
271 * @return the hash value.
272 * @throws IllegalArgumentException
273 * if the salt or prefix does not match the allowed pattern.
274 * @throws IllegalArgumentException
275 * when a {@link java.security.NoSuchAlgorithmException} is caught.
276 * @since 1.12
277 */
278 public static String md5Crypt(final byte[] keyBytes, final String salt, final String prefix, final Random random) {
279 final int keyLen = keyBytes.length;
280
281 // Extract the real salt from the given string which can be a complete hash string.
282 final String saltString;
283 if (salt == null) {
284 saltString = B64.getRandomSalt(8, random);
285 } else {
286 Objects.requireNonNull(prefix, "prefix");
287 if (prefix.length() < 3) {
288 throw new IllegalArgumentException("Invalid prefix value: " + prefix);
289 }
290 if (prefix.charAt(0) != '$' && prefix.charAt(prefix.length() - 1) != '$') {
291 throw new IllegalArgumentException("Invalid prefix value: " + prefix);
292 }
293 final Pattern p = Pattern.compile("^" + prefix.replace("$", "\\$") + "([\\.\\/a-zA-Z0-9]{1,8}).*");
294 final Matcher m = p.matcher(salt);
295 if (!m.find()) {
296 throw new IllegalArgumentException("Invalid salt value: " + salt);
297 }
298 saltString = m.group(1);
299 }
300 final byte[] saltBytes = saltString.getBytes(StandardCharsets.UTF_8);
301
302 final MessageDigest ctx = DigestUtils.getMd5Digest();
303
304 /*
305 * The password first, since that is what is most unknown
306 */
307 ctx.update(keyBytes);
308
309 /*
310 * Then our magic string
311 */
312 ctx.update(prefix.getBytes(StandardCharsets.UTF_8));
313
314 /*
315 * Then the raw salt
316 */
317 ctx.update(saltBytes);
318
319 /*
320 * Then just as many characters of the MD5(pw,salt,pw)
321 */
322 MessageDigest ctx1 = DigestUtils.getMd5Digest();
323 ctx1.update(keyBytes);
324 ctx1.update(saltBytes);
325 ctx1.update(keyBytes);
326 byte[] finalb = ctx1.digest();
327 int ii = keyLen;
328 while (ii > 0) {
329 ctx.update(finalb, 0, Math.min(ii, 16));
330 ii -= 16;
331 }
332
333 /*
334 * Don't leave anything around in JVM they could use.
335 */
336 Arrays.fill(finalb, (byte) 0);
337
338 /*
339 * Then something really weird...
340 */
341 ii = keyLen;
342 final int j = 0;
343 while (ii > 0) {
344 if ((ii & 1) == 1) {
345 ctx.update(finalb[j]);
346 } else {
347 ctx.update(keyBytes[j]);
348 }
349 ii >>= 1;
350 }
351
352 /*
353 * Now make the output string
354 */
355 final StringBuilder passwd = new StringBuilder(prefix + saltString + "$");
356 finalb = ctx.digest();
357
358 /*
359 * and now, just to make sure things don't run too fast On a 60 Mhz Pentium this takes 34 milliseconds, so you
360 * would need 30 seconds to build a 1000 entry dictionary...
361 */
362 for (int i = 0; i < ROUNDS; i++) {
363 ctx1 = DigestUtils.getMd5Digest();
364 if ((i & 1) != 0) {
365 ctx1.update(keyBytes);
366 } else {
367 ctx1.update(finalb, 0, BLOCKSIZE);
368 }
369
370 if (i % 3 != 0) {
371 ctx1.update(saltBytes);
372 }
373
374 if (i % 7 != 0) {
375 ctx1.update(keyBytes);
376 }
377
378 if ((i & 1) != 0) {
379 ctx1.update(finalb, 0, BLOCKSIZE);
380 } else {
381 ctx1.update(keyBytes);
382 }
383 finalb = ctx1.digest();
384 }
385
386 // The following was nearly identical to the Sha2Crypt code.
387 // Again, the buflen is not really needed.
388 // int buflen = MD5_PREFIX.length() - 1 + salt_string.length() + 1 + BLOCKSIZE + 1;
389 B64.b64from24bit(finalb[0], finalb[6], finalb[12], 4, passwd);
390 B64.b64from24bit(finalb[1], finalb[7], finalb[13], 4, passwd);
391 B64.b64from24bit(finalb[2], finalb[8], finalb[14], 4, passwd);
392 B64.b64from24bit(finalb[3], finalb[9], finalb[15], 4, passwd);
393 B64.b64from24bit(finalb[4], finalb[10], finalb[5], 4, passwd);
394 B64.b64from24bit((byte) 0, (byte) 0, finalb[11], 2, passwd);
395
396 /*
397 * Don't leave anything around in JVM they could use.
398 */
399 // Is there a better way to do this with the JVM?
400 ctx.reset();
401 ctx1.reset();
402 Arrays.fill(keyBytes, (byte) 0);
403 Arrays.fill(saltBytes, (byte) 0);
404 Arrays.fill(finalb, (byte) 0);
405
406 return passwd.toString();
407 }
408
409 /**
410 * TODO Make private in 2.0.
411 *
412 * @deprecated TODO Make private in 2.0.
413 */
414 @Deprecated
415 public Md5Crypt() {
416 // empty
417 }
418 }