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    *      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 }