Coverage Report

Created: 2025-06-13 06:43

/src/php-src/ext/standard/crypt.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
   +----------------------------------------------------------------------+
3
   | Copyright (c) The PHP Group                                          |
4
   +----------------------------------------------------------------------+
5
   | This source file is subject to version 3.01 of the PHP license,      |
6
   | that is bundled with this package in the file LICENSE, and is        |
7
   | available through the world-wide-web at the following url:           |
8
   | https://www.php.net/license/3_01.txt                                 |
9
   | If you did not receive a copy of the PHP license and are unable to   |
10
   | obtain it through the world-wide-web, please send a note to          |
11
   | license@php.net so we can mail you a copy immediately.               |
12
   +----------------------------------------------------------------------+
13
   | Authors: Stig Bakken <ssb@php.net>                                   |
14
   |          Zeev Suraski <zeev@php.net>                                 |
15
   |          Rasmus Lerdorf <rasmus@php.net>                             |
16
   |          Pierre Joye <pierre@php.net>                                |
17
   +----------------------------------------------------------------------+
18
*/
19
20
#include <stdlib.h>
21
22
#include "php.h"
23
24
#ifdef HAVE_UNISTD_H
25
#include <unistd.h>
26
#endif
27
#if PHP_USE_PHP_CRYPT_R
28
# include "php_crypt_r.h"
29
# include "crypt_freesec.h"
30
#else
31
# ifdef HAVE_CRYPT_H
32
#  if defined(CRYPT_R_GNU_SOURCE) && !defined(_GNU_SOURCE)
33
#   define _GNU_SOURCE
34
#  endif
35
#  include <crypt.h>
36
# endif
37
#endif
38
#include <time.h>
39
#include <string.h>
40
41
#ifdef PHP_WIN32
42
#include <process.h>
43
#endif
44
45
#include "php_crypt.h"
46
47
/* Used to check DES salts to ensure that they contain only valid characters */
48
0
#define IS_VALID_SALT_CHARACTER(c) (((c) >= '.' && (c) <= '9') || ((c) >= 'A' && (c) <= 'Z') || ((c) >= 'a' && (c) <= 'z'))
49
50
PHP_MINIT_FUNCTION(crypt) /* {{{ */
51
16
{
52
16
#if PHP_USE_PHP_CRYPT_R
53
16
  php_init_crypt_r();
54
16
#endif
55
56
16
  return SUCCESS;
57
16
}
58
/* }}} */
59
60
PHP_MSHUTDOWN_FUNCTION(crypt) /* {{{ */
61
0
{
62
0
#if PHP_USE_PHP_CRYPT_R
63
0
  php_shutdown_crypt_r();
64
0
#endif
65
66
0
  return SUCCESS;
67
0
}
68
/* }}} */
69
70
PHPAPI zend_string *php_crypt(const char *password, const int pass_len, const char *salt, int salt_len, bool quiet)
71
0
{
72
0
  char *crypt_res;
73
0
  zend_string *result;
74
75
0
  if (salt[0] == '*' && (salt[1] == '0' || salt[1] == '1')) {
76
0
    return NULL;
77
0
  }
78
79
/* Windows (win32/crypt) has a stripped down version of libxcrypt and
80
  a CryptoApi md5_crypt implementation */
81
0
#if PHP_USE_PHP_CRYPT_R
82
0
  {
83
0
    struct php_crypt_extended_data buffer;
84
85
0
    if (salt[0]=='$' && salt[1]=='1' && salt[2]=='$') {
86
0
      char output[MD5_HASH_MAX_LEN], *out;
87
88
0
      out = php_md5_crypt_r(password, salt, output);
89
0
      if (out) {
90
0
        return zend_string_init(out, strlen(out), 0);
91
0
      }
92
0
      return NULL;
93
0
    } else if (salt[0]=='$' && salt[1]=='6' && salt[2]=='$') {
94
0
      char *output;
95
0
      output = emalloc(PHP_MAX_SALT_LEN);
96
97
0
      crypt_res = php_sha512_crypt_r(password, salt, output, PHP_MAX_SALT_LEN);
98
0
      if (!crypt_res) {
99
0
        ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN);
100
0
        efree(output);
101
0
        return NULL;
102
0
      } else {
103
0
        result = zend_string_init(output, strlen(output), 0);
104
0
        ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN);
105
0
        efree(output);
106
0
        return result;
107
0
      }
108
0
    } else if (salt[0]=='$' && salt[1]=='5' && salt[2]=='$') {
109
0
      char *output;
110
0
      output = emalloc(PHP_MAX_SALT_LEN);
111
112
0
      crypt_res = php_sha256_crypt_r(password, salt, output, PHP_MAX_SALT_LEN);
113
0
      if (!crypt_res) {
114
0
        ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN);
115
0
        efree(output);
116
0
        return NULL;
117
0
      } else {
118
0
        result = zend_string_init(output, strlen(output), 0);
119
0
        ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN);
120
0
        efree(output);
121
0
        return result;
122
0
      }
123
0
    } else if (
124
0
        salt[0] == '$' &&
125
0
        salt[1] == '2' &&
126
0
        salt[2] != 0 &&
127
0
        salt[3] == '$') {
128
0
      char output[PHP_MAX_SALT_LEN + 1];
129
130
0
      memset(output, 0, PHP_MAX_SALT_LEN + 1);
131
132
0
      crypt_res = php_crypt_blowfish_rn(password, salt, output, sizeof(output));
133
0
      if (!crypt_res) {
134
0
        ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN + 1);
135
0
        return NULL;
136
0
      } else {
137
0
        result = zend_string_init(output, strlen(output), 0);
138
0
        ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN + 1);
139
0
        return result;
140
0
      }
141
0
    } else if (salt[0] == '_'
142
0
        || (IS_VALID_SALT_CHARACTER(salt[0]) && IS_VALID_SALT_CHARACTER(salt[1]))) {
143
      /* DES Fallback */
144
0
      memset(&buffer, 0, sizeof(buffer));
145
0
      _crypt_extended_init_r();
146
147
0
      crypt_res = _crypt_extended_r((const unsigned char *) password, salt, &buffer);
148
0
      if (!crypt_res || (salt[0] == '*' && salt[1] == '0')) {
149
0
        return NULL;
150
0
      } else {
151
0
        result = zend_string_init(crypt_res, strlen(crypt_res), 0);
152
0
        return result;
153
0
      }
154
0
    } else {
155
      /* Unknown hash type */
156
0
      return NULL;
157
0
    }
158
0
  }
159
#else
160
161
# if defined(HAVE_CRYPT_R) && (defined(_REENTRANT) || defined(_THREAD_SAFE))
162
#  if defined(CRYPT_R_STRUCT_CRYPT_DATA)
163
  struct crypt_data buffer;
164
  memset(&buffer, 0, sizeof(buffer));
165
#  elif defined(CRYPT_R_CRYPTD)
166
  CRYPTD buffer;
167
#  else
168
#   error Data struct used by crypt_r() is unknown. Please report.
169
#  endif
170
  crypt_res = crypt_r(password, salt, &buffer);
171
# elif defined(HAVE_CRYPT)
172
  crypt_res = crypt(password, salt);
173
# else
174
#  error No crypt() implementation
175
# endif
176
#endif
177
178
0
  if (!crypt_res || (salt[0] == '*' && salt[1] == '0')) {
179
0
    return NULL;
180
0
  }
181
0
  else if (!strcmp(crypt_res, "*")) {
182
    /* Musl crypt() uses "*" as a failure token rather
183
     * than the "*0" that libxcrypt/PHP use. Our test
184
     * suite in particular looks for "*0" in a few places,
185
     * and it would be annoying to handle both values
186
     * explicitly. It seems wise to abstract this detail
187
     * from the end user: if it's annoying for us, imagine
188
     * how annoying it would be in end-user code; not that
189
     * anyone would think of it. */
190
0
    return NULL;
191
0
  }
192
0
  else {
193
0
    result = zend_string_init(crypt_res, strlen(crypt_res), 0);
194
0
    return result;
195
0
  }
196
0
}
197
/* }}} */
198
199
200
/* {{{ Hash a string */
201
PHP_FUNCTION(crypt)
202
0
{
203
0
  char salt[PHP_MAX_SALT_LEN + 1];
204
0
  char *str, *salt_in = NULL;
205
0
  size_t str_len, salt_in_len = 0;
206
0
  zend_string *result;
207
208
0
  ZEND_PARSE_PARAMETERS_START(2, 2)
209
0
    Z_PARAM_STRING(str, str_len)
210
0
    Z_PARAM_STRING(salt_in, salt_in_len)
211
0
  ZEND_PARSE_PARAMETERS_END();
212
213
0
  salt[0] = salt[PHP_MAX_SALT_LEN] = '\0';
214
215
  /* This will produce suitable results if people depend on DES-encryption
216
   * available (passing always 2-character salt). At least for glibc6.1 */
217
0
  memset(&salt[1], '$', PHP_MAX_SALT_LEN - 1);
218
0
  memcpy(salt, salt_in, MIN(PHP_MAX_SALT_LEN, salt_in_len));
219
220
0
  salt_in_len = MIN(PHP_MAX_SALT_LEN, salt_in_len);
221
0
  salt[salt_in_len] = '\0';
222
223
0
  if ((result = php_crypt(str, (int)str_len, salt, (int)salt_in_len, 0)) == NULL) {
224
0
    if (salt[0] == '*' && salt[1] == '0') {
225
0
      RETURN_STRING("*1");
226
0
    } else {
227
0
      RETURN_STRING("*0");
228
0
    }
229
0
  }
230
0
  RETURN_STR(result);
231
0
}
232
/* }}} */