Coverage Report

Created: 2026-06-02 06:36

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