1  /* One way encryption based on MD5 sum.
       2     Compatible with the behavior of MD5 crypt introduced in FreeBSD 2.0.
       3  
       4     Copyright (C) 1996-2017 Free Software Foundation, Inc.
       5     Modified by Björn Esser <besser82 at fedoraproject.org> in 2020.
       6  
       7     This library is free software; you can redistribute it and/or
       8     modify it under the terms of the GNU Lesser General Public License
       9     as published by the Free Software Foundation; either version 2.1 of
      10     the License, or (at your option) any later version.
      11  
      12     This library is distributed in the hope that it will be useful,
      13     but WITHOUT ANY WARRANTY; without even the implied warranty of
      14     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      15     GNU Lesser General Public License for more details.
      16  
      17     You should have received a copy of the GNU Lesser General Public
      18     License along with this library; if not, see
      19     <https://www.gnu.org/licenses/>.  */
      20  
      21  #include "crypt-port.h"
      22  #include "alg-md5.h"
      23  
      24  #include <errno.h>
      25  
      26  #if INCLUDE_md5crypt
      27  
      28  /* Define our magic string to mark salt for MD5 "encryption"
      29     replacement.  This is meant to be the same as for other MD5 based
      30     encryption implementations.  */
      31  static const char md5_salt_prefix[] = "$1$";
      32  
      33  /* The maximum length of an MD5 salt string (just the actual salt, not
      34     the entire prefix).  */
      35  #define SALT_LEN_MAX 8
      36  
      37  /* The length of an MD5-hashed password string, including the
      38     terminating NUL character.  Prefix (including its NUL) + 8 bytes of
      39     salt + separator + 22 bytes of hashed password.  */
      40  #define MD5_HASH_LENGTH \
      41    (sizeof (md5_salt_prefix) + SALT_LEN_MAX + 1 + 22)
      42  
      43  static_assert (MD5_HASH_LENGTH <= CRYPT_OUTPUT_SIZE,
      44                 "CRYPT_OUTPUT_SIZE is too small for MD5");
      45  
      46  /* An md5_buffer holds all of the sensitive intermediate data.  */
      47  struct md5_buffer
      48  {
      49    MD5_CTX ctx;
      50    uint8_t result[16];
      51  };
      52  
      53  static_assert (sizeof (struct md5_buffer) <= ALG_SPECIFIC_SIZE,
      54                 "ALG_SPECIFIC_SIZE is too small for MD5");
      55  
      56  
      57  /* This entry point is equivalent to the `crypt' function in Unix
      58     libcs.  */
      59  void
      60  crypt_md5crypt_rn (const char *phrase, size_t phr_size,
      61                     const char *setting, size_t ARG_UNUSED (set_size),
      62                     uint8_t *output, size_t out_size,
      63                     void *scratch, size_t scr_size)
      64  {
      65    /* This shouldn't ever happen, but...  */
      66    if (out_size < MD5_HASH_LENGTH || scr_size < sizeof (struct md5_buffer))
      67      {
      68        errno = ERANGE;
      69        return;
      70      }
      71  
      72    struct md5_buffer *buf = scratch;
      73    MD5_CTX *ctx = &buf->ctx;
      74    uint8_t *result = buf->result;
      75    char *cp = (char *)output;
      76    const char *salt = setting;
      77  
      78    size_t salt_size;
      79    size_t cnt;
      80  
      81    /* Find beginning of salt string.  The prefix should normally always
      82       be present.  Just in case it is not.  */
      83    if (strncmp (md5_salt_prefix, salt, sizeof (md5_salt_prefix) - 1) == 0)
      84      /* Skip salt prefix.  */
      85      salt += sizeof (md5_salt_prefix) - 1;
      86  
      87  
      88    /* The salt ends at the next '$' or the end of the string.
      89       Ensure ':' does not appear in the salt (it is used as a separator in /etc/passwd).
      90       Also check for '\n', as in /etc/passwd the whole parameters of the user data must
      91       be on a single line. */
      92    salt_size = strcspn (salt, "$:\n");
      93    if (!(salt[salt_size] == '$' || !salt[salt_size]))
      94      {
      95        errno = EINVAL;
      96        return;
      97      }
      98  
      99    /* Ensure we do not use more salt than SALT_LEN_MAX. */
     100    if (salt_size > SALT_LEN_MAX)
     101      salt_size = SALT_LEN_MAX;
     102  
     103    /* Compute alternate MD5 sum with input PHRASE, SALT, and PHRASE.  The
     104       final result will be added to the first context.  */
     105    MD5_Init (ctx);
     106  
     107    /* Add phrase.  */
     108    MD5_Update (ctx, phrase, phr_size);
     109  
     110    /* Add salt.  */
     111    MD5_Update (ctx, salt, salt_size);
     112  
     113    /* Add phrase again.  */
     114    MD5_Update (ctx, phrase, phr_size);
     115  
     116    /* Now get result of this (16 bytes).  */
     117    MD5_Final (result, ctx);
     118  
     119    /* Prepare for the real work.  */
     120    MD5_Init (ctx);
     121  
     122    /* Add the phrase string.  */
     123    MD5_Update (ctx, phrase, phr_size);
     124  
     125    /* Because the SALT argument need not always have the salt prefix we
     126       add it separately.  */
     127    MD5_Update (ctx, md5_salt_prefix, sizeof (md5_salt_prefix) - 1);
     128  
     129    /* The last part is the salt string.  This must be at most 8
     130       characters and it ends at the first `$' character (for
     131       compatibility with existing implementations).  */
     132    MD5_Update (ctx, salt, salt_size);
     133  
     134  
     135    /* Add for any character in the phrase one byte of the alternate sum.  */
     136    for (cnt = phr_size; cnt > 16; cnt -= 16)
     137      MD5_Update (ctx, result, 16);
     138    MD5_Update (ctx, result, cnt);
     139  
     140    /* For the following code we need a NUL byte.  */
     141    *result = '\0';
     142  
     143    /* The original implementation now does something weird: for every 1
     144       bit in the phrase the first 0 is added to the buffer, for every 0
     145       bit the first character of the phrase.  This does not seem to be
     146       what was intended but we have to follow this to be compatible.  */
     147    for (cnt = phr_size; cnt > 0; cnt >>= 1)
     148      MD5_Update (ctx, (cnt & 1) != 0 ? (const char *) result : phrase, 1);
     149  
     150    /* Create intermediate result.  */
     151    MD5_Final (result, ctx);
     152  
     153    /* Now comes another weirdness.  In fear of password crackers here
     154       comes a quite long loop which just processes the output of the
     155       previous round again.  We cannot ignore this here.  */
     156    for (cnt = 0; cnt < 1000; ++cnt)
     157      {
     158        /* New context.  */
     159        MD5_Init (ctx);
     160  
     161        /* Add phrase or last result.  */
     162        if ((cnt & 1) != 0)
     163          MD5_Update (ctx, phrase, phr_size);
     164        else
     165          MD5_Update (ctx, result, 16);
     166  
     167        /* Add salt for numbers not divisible by 3.  */
     168        if (cnt % 3 != 0)
     169          MD5_Update (ctx, salt, salt_size);
     170  
     171        /* Add phrase for numbers not divisible by 7.  */
     172        if (cnt % 7 != 0)
     173          MD5_Update (ctx, phrase, phr_size);
     174  
     175        /* Add phrase or last result.  */
     176        if ((cnt & 1) != 0)
     177          MD5_Update (ctx, result, 16);
     178        else
     179          MD5_Update (ctx, phrase, phr_size);
     180  
     181        /* Create intermediate result.  */
     182        MD5_Final (result, ctx);
     183      }
     184  
     185    /* Now we can construct the result string.  It consists of three
     186       parts.  We already know that there is enough space at CP.  */
     187    memcpy (cp, md5_salt_prefix, sizeof (md5_salt_prefix) - 1);
     188    cp += sizeof (md5_salt_prefix) - 1;
     189  
     190    memcpy (cp, salt, salt_size);
     191    cp += salt_size;
     192    *cp++ = '$';
     193  
     194  #define b64_from_24bit(B2, B1, B0, N)                   \
     195    do {                                                  \
     196      unsigned int w = ((((unsigned int)(B2)) << 16) |    \
     197                        (((unsigned int)(B1)) << 8) |     \
     198                        ((unsigned int)(B0)));            \
     199      int n = (N);                                        \
     200      while (n-- > 0)                                     \
     201        {                                                 \
     202          *cp++ = b64t[w & 0x3f];                         \
     203          w >>= 6;                                        \
     204        }                                                 \
     205    } while (0)
     206  
     207  
     208    b64_from_24bit (result[0], result[6], result[12], 4);
     209    b64_from_24bit (result[1], result[7], result[13], 4);
     210    b64_from_24bit (result[2], result[8], result[14], 4);
     211    b64_from_24bit (result[3], result[9], result[15], 4);
     212    b64_from_24bit (result[4], result[10], result[5], 4);
     213    b64_from_24bit (0, 0, result[11], 2);
     214  
     215    *cp = '\0';
     216  }
     217  
     218  void
     219  gensalt_md5crypt_rn (unsigned long count,
     220                       const uint8_t *rbytes, size_t nrbytes,
     221                       uint8_t *output, size_t output_size)
     222  {
     223    if (count != 0)
     224      {
     225        errno = EINVAL;
     226        return;
     227      }
     228    gensalt_sha_rn ('1', 8, 1000, 1000, 1000, 1000,
     229                    rbytes, nrbytes, output, output_size);
     230  }
     231  
     232  #endif