Md5Crypt.java

  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. import java.nio.charset.StandardCharsets;
  19. import java.security.MessageDigest;
  20. import java.security.SecureRandom;
  21. import java.util.Arrays;
  22. import java.util.Objects;
  23. import java.util.Random;
  24. import java.util.regex.Matcher;
  25. import java.util.regex.Pattern;

  26. /**
  27.  * The libc crypt() "$1$" and Apache "$apr1$" MD5-based hash algorithm.
  28.  * <p>
  29.  * Based on the public domain ("beer-ware") C implementation from Poul-Henning Kamp which was found at: <a
  30.  * href="http://www.freebsd.org/cgi/cvsweb.cgi/src/lib/libcrypt/crypt-md5.c?rev=1.1;content-type=text%2Fplain">
  31.  * crypt-md5.c @ freebsd.org</a>
  32.  * </p>
  33.  * <p>
  34.  * Source:
  35.  * </p>
  36.  * <pre>
  37.  * $FreeBSD: src/lib/libcrypt/crypt-md5.c,v 1.1 1999/01/21 13:50:09 brandon Exp $
  38.  * </pre>
  39.  * <p>
  40.  * Conversion to Kotlin and from there to Java in 2012.
  41.  * </p>
  42.  * <p>
  43.  * The C style comments are from the original C code, the ones with "//" from the port.
  44.  * </p>
  45.  * <p>
  46.  * This class is immutable and thread-safe.
  47.  * </p>
  48.  *
  49.  * @since 1.7
  50.  */
  51. public class Md5Crypt {

  52.     /** The Identifier of the Apache variant. */
  53.     static final String APR1_PREFIX = "$apr1$";

  54.     /** The number of bytes of the final hash. */
  55.     private static final int BLOCKSIZE = 16;

  56.     /** The Identifier of this crypt() variant. */
  57.     static final String MD5_PREFIX = "$1$";

  58.     /** The number of rounds of the big loop. */
  59.     private static final int ROUNDS = 1000;

  60.     /**
  61.      * See {@link #apr1Crypt(byte[], String)} for details.
  62.      * <p>
  63.      * A salt is generated for you using {@link SecureRandom}; your own {@link Random} in
  64.      * {@link #apr1Crypt(byte[], Random)}.
  65.      * </p>
  66.      *
  67.      * @param keyBytes plaintext string to hash. Each array element is set to {@code 0} before returning.
  68.      * @return the hash value
  69.      * @throws IllegalArgumentException when a {@link java.security.NoSuchAlgorithmException} is caught. *
  70.      * @see #apr1Crypt(byte[], String)
  71.      */
  72.     public static String apr1Crypt(final byte[] keyBytes) {
  73.         return apr1Crypt(keyBytes, APR1_PREFIX + B64.getRandomSalt(8));
  74.     }

  75.     /**
  76.      * See {@link #apr1Crypt(byte[], String)} for details.
  77.      * <p>
  78.      * A salt is generated for you using the user provided {@link Random}.
  79.      * </p>
  80.      *
  81.      * @param keyBytes plaintext string to hash. Each array element is set to {@code 0} before returning.
  82.      * @param random the instance of {@link Random} to use for generating the salt.
  83.      *              Consider using {@link SecureRandom} for more secure salts.
  84.      * @return the hash value
  85.      * @throws IllegalArgumentException when a {@link java.security.NoSuchAlgorithmException} is caught. *
  86.      * @see #apr1Crypt(byte[], String)
  87.      * @since 1.12
  88.      */
  89.     public static String apr1Crypt(final byte[] keyBytes, final Random random) {
  90.         return apr1Crypt(keyBytes, APR1_PREFIX + B64.getRandomSalt(8, random));
  91.     }

  92.     /**
  93.      * See {@link #apr1Crypt(String, String)} for details.
  94.      * <p>
  95.      * A salt is generated for you using {@link SecureRandom}
  96.      * </p>
  97.      *
  98.      * @param keyBytes
  99.      *            plaintext string to hash. Each array element is set to {@code 0} before returning.
  100.      * @param salt
  101.      *            An APR1 salt. The salt may be null, in which case a salt is generated for you using
  102.      *            {@link SecureRandom}
  103.      * @return the hash value
  104.      * @throws IllegalArgumentException
  105.      *             if the salt does not match the allowed pattern
  106.      * @throws IllegalArgumentException
  107.      *             when a {@link java.security.NoSuchAlgorithmException} is caught.
  108.      */
  109.     public static String apr1Crypt(final byte[] keyBytes, String salt) {
  110.         // to make the md5Crypt regex happy
  111.         if (salt != null && !salt.startsWith(APR1_PREFIX)) {
  112.             salt = APR1_PREFIX + salt;
  113.         }
  114.         return Md5Crypt.md5Crypt(keyBytes, salt, APR1_PREFIX);
  115.     }

  116.     /**
  117.      * See {@link #apr1Crypt(String, String)} for details.
  118.      * <p>
  119.      * A salt is generated for you using {@link SecureRandom}.
  120.      * </p>
  121.      *
  122.      * @param keyBytes
  123.      *            plaintext string to hash. Each array element is set to {@code 0} before returning.
  124.      * @return the hash value
  125.      * @throws IllegalArgumentException
  126.      *             when a {@link java.security.NoSuchAlgorithmException} is caught.
  127.      * @see #apr1Crypt(byte[], String)
  128.      */
  129.     public static String apr1Crypt(final String keyBytes) {
  130.         return apr1Crypt(keyBytes.getBytes(StandardCharsets.UTF_8));
  131.     }

  132.     /**
  133.      * Generates an Apache htpasswd compatible "$apr1$" MD5 based hash value.
  134.      * <p>
  135.      * The algorithm is identical to the crypt(3) "$1$" one but produces different outputs due to the different salt
  136.      * prefix.
  137.      * </p>
  138.      *
  139.      * @param keyBytes
  140.      *            plaintext string to hash. Each array element is set to {@code 0} before returning.
  141.      * @param salt
  142.      *            salt string including the prefix and optionally garbage at the end. The salt may be null, in which
  143.      *            case a salt is generated for you using {@link SecureRandom}.
  144.      * @return the hash value
  145.      * @throws IllegalArgumentException
  146.      *             if the salt does not match the allowed pattern
  147.      * @throws IllegalArgumentException
  148.      *             when a {@link java.security.NoSuchAlgorithmException} is caught.
  149.      */
  150.     public static String apr1Crypt(final String keyBytes, final String salt) {
  151.         return apr1Crypt(keyBytes.getBytes(StandardCharsets.UTF_8), salt);
  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.      * @param keyBytes
  162.      *            plaintext string to hash. Each array element is set to {@code 0} before returning.
  163.      * @return the hash value
  164.      * @throws IllegalArgumentException
  165.      *             when a {@link java.security.NoSuchAlgorithmException} is caught.
  166.      * @see #md5Crypt(byte[], String)
  167.      */
  168.     public static String md5Crypt(final byte[] keyBytes) {
  169.         return md5Crypt(keyBytes, MD5_PREFIX + B64.getRandomSalt(8));
  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.      * @param keyBytes
  180.      *            plaintext string to hash. Each array element is set to {@code 0} before returning.
  181.      * @param random
  182.      *            the instance of {@link Random} to use for generating the salt.
  183.      *            Consider using {@link SecureRandom} for more secure salts.
  184.      * @return the hash value
  185.      * @throws IllegalArgumentException
  186.      *             when a {@link java.security.NoSuchAlgorithmException} is caught.
  187.      * @see #md5Crypt(byte[], String)
  188.      * @since 1.12
  189.      */
  190.     public static String md5Crypt(final byte[] keyBytes, final Random random) {
  191.         return md5Crypt(keyBytes, MD5_PREFIX + B64.getRandomSalt(8, random));
  192.     }

  193.     /**
  194.      * Generates a libc crypt() compatible "$1$" MD5 based hash value.
  195.      * <p>
  196.      * See {@link Crypt#crypt(String, String)} for details. We use {@link SecureRandom} for seed generation by
  197.      * default.
  198.      * </p>
  199.      *
  200.      * @param keyBytes
  201.      *            plaintext string to hash. Each array element is set to {@code 0} before returning.
  202.      * @param salt
  203.      *            salt string including the prefix and optionally garbage at the end. The salt may be null, in which
  204.      *            case a salt is generated for you using {@link SecureRandom}.
  205.      * @return the hash value
  206.      * @throws IllegalArgumentException
  207.      *             if the salt does not match the allowed pattern
  208.      * @throws IllegalArgumentException
  209.      *             when a {@link java.security.NoSuchAlgorithmException} is caught.
  210.      */
  211.     public static String md5Crypt(final byte[] keyBytes, final String salt) {
  212.         return md5Crypt(keyBytes, salt, MD5_PREFIX);
  213.     }

  214.     /**
  215.      * Generates a libc6 crypt() "$1$" or Apache htpasswd "$apr1$" hash value.
  216.      * <p>
  217.      * See {@link Crypt#crypt(String, String)} or {@link #apr1Crypt(String, String)} for details. We use
  218.      * {@link SecureRandom by default}.
  219.      * </p>
  220.      *
  221.      * @param keyBytes
  222.      *            plaintext string to hash. Each array element is set to {@code 0} before returning.
  223.      * @param salt
  224.      *            real salt value without prefix or "rounds=". The salt may be null, in which case a salt
  225.      *            is generated for you using {@link SecureRandom}.
  226.      * @param prefix
  227.      *            The salt prefix {@value #APR1_PREFIX}, {@value #MD5_PREFIX}.
  228.      * @return the hash value
  229.      * @throws IllegalArgumentException
  230.      *             if the salt does not match the allowed pattern
  231.      * @throws IllegalArgumentException
  232.      *             when a {@link java.security.NoSuchAlgorithmException} is caught.
  233.      */
  234.     public static String md5Crypt(final byte[] keyBytes, final String salt, final String prefix) {
  235.         return md5Crypt(keyBytes, salt, prefix, new SecureRandom());
  236.     }

  237.     /**
  238.      * Generates a libc6 crypt() "$1$" or Apache htpasswd "$apr1$" hash value.
  239.      * <p>
  240.      * See {@link Crypt#crypt(String, String)} or {@link #apr1Crypt(String, String)} for details.
  241.      * </p>
  242.      *
  243.      * @param keyBytes
  244.      *            plaintext string to hash. Each array element is set to {@code 0} before returning.
  245.      * @param salt
  246.      *            real salt value without prefix or "rounds=". The salt may be null, in which case a salt
  247.      *            is generated for you using {@link SecureRandom}.
  248.      * @param prefix
  249.      *            The salt prefix {@value #APR1_PREFIX}, {@value #MD5_PREFIX}.
  250.      * @param random
  251.      *            the instance of {@link Random} to use for generating the salt.
  252.      *            Consider using {@link SecureRandom} for more secure salts.
  253.      * @return the hash value
  254.      * @throws IllegalArgumentException
  255.      *             if the salt or prefix does not match the allowed pattern
  256.      * @throws IllegalArgumentException
  257.      *             when a {@link java.security.NoSuchAlgorithmException} is caught.
  258.      * @since 1.12
  259.      */
  260.     public static String md5Crypt(final byte[] keyBytes, final String salt, final String prefix, final Random random) {
  261.         final int keyLen = keyBytes.length;

  262.         // Extract the real salt from the given string which can be a complete hash string.
  263.         final String saltString;
  264.         if (salt == null) {
  265.             saltString = B64.getRandomSalt(8, random);
  266.         } else {
  267.             Objects.requireNonNull(prefix, "prefix");
  268.             if (prefix.length() < 3) {
  269.                 throw new IllegalArgumentException("Invalid prefix value: " + prefix);
  270.             }
  271.             if (prefix.charAt(0) != '$' && prefix.charAt(prefix.length() - 1) != '$') {
  272.                 throw new IllegalArgumentException("Invalid prefix value: " + prefix);
  273.             }
  274.             final Pattern p = Pattern.compile("^" + prefix.replace("$", "\\$") + "([\\.\\/a-zA-Z0-9]{1,8}).*");
  275.             final Matcher m = p.matcher(salt);
  276.             if (!m.find()) {
  277.                 throw new IllegalArgumentException("Invalid salt value: " + salt);
  278.             }
  279.             saltString = m.group(1);
  280.         }
  281.         final byte[] saltBytes = saltString.getBytes(StandardCharsets.UTF_8);

  282.         final MessageDigest ctx = DigestUtils.getMd5Digest();

  283.         /*
  284.          * The password first, since that is what is most unknown
  285.          */
  286.         ctx.update(keyBytes);

  287.         /*
  288.          * Then our magic string
  289.          */
  290.         ctx.update(prefix.getBytes(StandardCharsets.UTF_8));

  291.         /*
  292.          * Then the raw salt
  293.          */
  294.         ctx.update(saltBytes);

  295.         /*
  296.          * Then just as many characters of the MD5(pw,salt,pw)
  297.          */
  298.         MessageDigest ctx1 = DigestUtils.getMd5Digest();
  299.         ctx1.update(keyBytes);
  300.         ctx1.update(saltBytes);
  301.         ctx1.update(keyBytes);
  302.         byte[] finalb = ctx1.digest();
  303.         int ii = keyLen;
  304.         while (ii > 0) {
  305.             ctx.update(finalb, 0, Math.min(ii, 16));
  306.             ii -= 16;
  307.         }

  308.         /*
  309.          * Don't leave anything around in JVM they could use.
  310.          */
  311.         Arrays.fill(finalb, (byte) 0);

  312.         /*
  313.          * Then something really weird...
  314.          */
  315.         ii = keyLen;
  316.         final int j = 0;
  317.         while (ii > 0) {
  318.             if ((ii & 1) == 1) {
  319.                 ctx.update(finalb[j]);
  320.             } else {
  321.                 ctx.update(keyBytes[j]);
  322.             }
  323.             ii >>= 1;
  324.         }

  325.         /*
  326.          * Now make the output string
  327.          */
  328.         final StringBuilder passwd = new StringBuilder(prefix + saltString + "$");
  329.         finalb = ctx.digest();

  330.         /*
  331.          * and now, just to make sure things don't run too fast On a 60 Mhz Pentium this takes 34 milliseconds, so you
  332.          * would need 30 seconds to build a 1000 entry dictionary...
  333.          */
  334.         for (int i = 0; i < ROUNDS; i++) {
  335.             ctx1 = DigestUtils.getMd5Digest();
  336.             if ((i & 1) != 0) {
  337.                 ctx1.update(keyBytes);
  338.             } else {
  339.                 ctx1.update(finalb, 0, BLOCKSIZE);
  340.             }

  341.             if (i % 3 != 0) {
  342.                 ctx1.update(saltBytes);
  343.             }

  344.             if (i % 7 != 0) {
  345.                 ctx1.update(keyBytes);
  346.             }

  347.             if ((i & 1) != 0) {
  348.                 ctx1.update(finalb, 0, BLOCKSIZE);
  349.             } else {
  350.                 ctx1.update(keyBytes);
  351.             }
  352.             finalb = ctx1.digest();
  353.         }

  354.         // The following was nearly identical to the Sha2Crypt code.
  355.         // Again, the buflen is not really needed.
  356.         // int buflen = MD5_PREFIX.length() - 1 + salt_string.length() + 1 + BLOCKSIZE + 1;
  357.         B64.b64from24bit(finalb[0], finalb[6], finalb[12], 4, passwd);
  358.         B64.b64from24bit(finalb[1], finalb[7], finalb[13], 4, passwd);
  359.         B64.b64from24bit(finalb[2], finalb[8], finalb[14], 4, passwd);
  360.         B64.b64from24bit(finalb[3], finalb[9], finalb[15], 4, passwd);
  361.         B64.b64from24bit(finalb[4], finalb[10], finalb[5], 4, passwd);
  362.         B64.b64from24bit((byte) 0, (byte) 0, finalb[11], 2, passwd);

  363.         /*
  364.          * Don't leave anything around in JVM they could use.
  365.          */
  366.         // Is there a better way to do this with the JVM?
  367.         ctx.reset();
  368.         ctx1.reset();
  369.         Arrays.fill(keyBytes, (byte) 0);
  370.         Arrays.fill(saltBytes, (byte) 0);
  371.         Arrays.fill(finalb, (byte) 0);

  372.         return passwd.toString();
  373.     }

  374.     /**
  375.      * TODO Make private in 2.0.
  376.      *
  377.      * @deprecated TODO Make private in 2.0.
  378.      */
  379.     @Deprecated
  380.     public Md5Crypt() {
  381.         // empty
  382.     }
  383. }