001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      https://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.codec.digest;
019
020import java.nio.charset.StandardCharsets;
021import java.security.MessageDigest;
022import java.security.NoSuchAlgorithmException;
023import java.security.SecureRandom;
024import java.util.Arrays;
025import java.util.Objects;
026import java.util.Random;
027import java.util.regex.Matcher;
028import java.util.regex.Pattern;
029
030/**
031 * The libc crypt() "$1$" and Apache "$apr1$" MD5-based hash algorithm.
032 * <p>
033 * Based on the public domain ("beer-ware") C implementation from Poul-Henning Kamp which was found at:
034 * <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>
035 * </p>
036 * <p>
037 * Source:
038 * </p>
039 *
040 * <pre>
041 * $FreeBSD: src/lib/libcrypt/crypt-md5.c,v 1.1 1999/01/21 13:50:09 brandon Exp $
042 * </pre>
043 * <p>
044 * Conversion to Kotlin and from there to Java in 2012.
045 * </p>
046 * <p>
047 * The C style comments are from the original C code, the ones with "//" from the port.
048 * </p>
049 * <p>
050 * This class is immutable and thread-safe.
051 * </p>
052 *
053 * @since 1.7
054 */
055public class Md5Crypt {
056
057    /** The Identifier of the Apache variant. */
058    static final String APR1_PREFIX = "$apr1$";
059
060    /** The number of bytes of the final hash. */
061    private static final int BLOCKSIZE = 16;
062
063    /** The Identifier of this crypt() variant. */
064    static final String MD5_PREFIX = "$1$";
065
066    /** The number of rounds of the big loop. */
067    private static final int ROUNDS = 1000;
068
069    /**
070     * See {@link #apr1Crypt(byte[], String)} for details.
071     * <p>
072     * A salt is generated for you using {@link SecureRandom}; your own {@link Random} in {@link #apr1Crypt(byte[], Random)}.
073     * </p>
074     *
075     * @param keyBytes The plaintext bytes to hash. Each array element is set to {@code 0} before returning.
076     * @return The hash value.
077     * @throws IllegalArgumentException Thrown if a {@link NoSuchAlgorithmException} is caught.
078     * @see #apr1Crypt(byte[], String)
079     */
080    public static String apr1Crypt(final byte[] keyBytes) {
081        return apr1Crypt(keyBytes, APR1_PREFIX + B64.getRandomSalt(8));
082    }
083
084    /**
085     * See {@link #apr1Crypt(byte[], String)} for details.
086     * <p>
087     * A salt is generated for you using the user provided {@link Random}.
088     * </p>
089     *
090     * @param keyBytes The plaintext bytes to hash. Each array element is set to {@code 0} before returning.
091     * @param random   The instance of {@link Random} to use for generating the salt. Consider using {@link SecureRandom} for more secure salts.
092     * @return The hash value.
093     * @throws IllegalArgumentException Thrown if a {@link NoSuchAlgorithmException} is caught.
094     * @see #apr1Crypt(byte[], String)
095     * @since 1.12
096     */
097    public static String apr1Crypt(final byte[] keyBytes, final Random random) {
098        return apr1Crypt(keyBytes, APR1_PREFIX + B64.getRandomSalt(8, random));
099    }
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}