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