xref: /PHP-8.3/ext/standard/crypt.c (revision a92acbad)
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 #include "ext/random/php_random.h"
47 
48 /* Used to check DES salts to ensure that they contain only valid characters */
49 #define IS_VALID_SALT_CHARACTER(c) (((c) >= '.' && (c) <= '9') || ((c) >= 'A' && (c) <= 'Z') || ((c) >= 'a' && (c) <= 'z'))
50 
PHP_MINIT_FUNCTION(crypt)51 PHP_MINIT_FUNCTION(crypt) /* {{{ */
52 {
53 #if PHP_USE_PHP_CRYPT_R
54 	php_init_crypt_r();
55 #endif
56 
57 	return SUCCESS;
58 }
59 /* }}} */
60 
PHP_MSHUTDOWN_FUNCTION(crypt)61 PHP_MSHUTDOWN_FUNCTION(crypt) /* {{{ */
62 {
63 #if PHP_USE_PHP_CRYPT_R
64 	php_shutdown_crypt_r();
65 #endif
66 
67 	return SUCCESS;
68 }
69 /* }}} */
70 
php_crypt(const char * password,const int pass_len,const char * salt,int salt_len,bool quiet)71 PHPAPI zend_string *php_crypt(const char *password, const int pass_len, const char *salt, int salt_len, bool quiet)
72 {
73 	char *crypt_res;
74 	zend_string *result;
75 
76 	if (salt[0] == '*' && (salt[1] == '0' || salt[1] == '1')) {
77 		return NULL;
78 	}
79 
80 /* Windows (win32/crypt) has a stripped down version of libxcrypt and
81 	a CryptoApi md5_crypt implementation */
82 #if PHP_USE_PHP_CRYPT_R
83 	{
84 		struct php_crypt_extended_data buffer;
85 
86 		if (salt[0]=='$' && salt[1]=='1' && salt[2]=='$') {
87 			char output[MD5_HASH_MAX_LEN], *out;
88 
89 			out = php_md5_crypt_r(password, salt, output);
90 			if (out) {
91 				return zend_string_init(out, strlen(out), 0);
92 			}
93 			return NULL;
94 		} else if (salt[0]=='$' && salt[1]=='6' && salt[2]=='$') {
95 			char *output;
96 			output = emalloc(PHP_MAX_SALT_LEN);
97 
98 			crypt_res = php_sha512_crypt_r(password, salt, output, PHP_MAX_SALT_LEN);
99 			if (!crypt_res) {
100 				ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN);
101 				efree(output);
102 				return NULL;
103 			} else {
104 				result = zend_string_init(output, strlen(output), 0);
105 				ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN);
106 				efree(output);
107 				return result;
108 			}
109 		} else if (salt[0]=='$' && salt[1]=='5' && salt[2]=='$') {
110 			char *output;
111 			output = emalloc(PHP_MAX_SALT_LEN);
112 
113 			crypt_res = php_sha256_crypt_r(password, salt, output, PHP_MAX_SALT_LEN);
114 			if (!crypt_res) {
115 				ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN);
116 				efree(output);
117 				return NULL;
118 			} else {
119 				result = zend_string_init(output, strlen(output), 0);
120 				ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN);
121 				efree(output);
122 				return result;
123 			}
124 		} else if (
125 				salt[0] == '$' &&
126 				salt[1] == '2' &&
127 				salt[2] != 0 &&
128 				salt[3] == '$') {
129 			char output[PHP_MAX_SALT_LEN + 1];
130 
131 			memset(output, 0, PHP_MAX_SALT_LEN + 1);
132 
133 			crypt_res = php_crypt_blowfish_rn(password, salt, output, sizeof(output));
134 			if (!crypt_res) {
135 				ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN + 1);
136 				return NULL;
137 			} else {
138 				result = zend_string_init(output, strlen(output), 0);
139 				ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN + 1);
140 				return result;
141 			}
142 		} else if (salt[0] == '_'
143 				|| (IS_VALID_SALT_CHARACTER(salt[0]) && IS_VALID_SALT_CHARACTER(salt[1]))) {
144 			/* DES Fallback */
145 			memset(&buffer, 0, sizeof(buffer));
146 			_crypt_extended_init_r();
147 
148 			crypt_res = _crypt_extended_r((const unsigned char *) password, salt, &buffer);
149 			if (!crypt_res || (salt[0] == '*' && salt[1] == '0')) {
150 				return NULL;
151 			} else {
152 				result = zend_string_init(crypt_res, strlen(crypt_res), 0);
153 				return result;
154 			}
155 		} else {
156 			/* Unknown hash type */
157 			return NULL;
158 		}
159 	}
160 #else
161 
162 # if defined(HAVE_CRYPT_R) && (defined(_REENTRANT) || defined(_THREAD_SAFE))
163 #  if defined(CRYPT_R_STRUCT_CRYPT_DATA)
164 	struct crypt_data buffer;
165 	memset(&buffer, 0, sizeof(buffer));
166 #  elif defined(CRYPT_R_CRYPTD)
167 	CRYPTD buffer;
168 #  else
169 #   error Data struct used by crypt_r() is unknown. Please report.
170 #  endif
171 	crypt_res = crypt_r(password, salt, &buffer);
172 # elif defined(HAVE_CRYPT)
173 	crypt_res = crypt(password, salt);
174 # else
175 #  error No crypt() implementation
176 # endif
177 #endif
178 
179 	if (!crypt_res || (salt[0] == '*' && salt[1] == '0')) {
180 		return NULL;
181 	} else {
182 		result = zend_string_init(crypt_res, strlen(crypt_res), 0);
183 		return result;
184 	}
185 }
186 /* }}} */
187 
188 
189 /* {{{ Hash a string */
PHP_FUNCTION(crypt)190 PHP_FUNCTION(crypt)
191 {
192 	char salt[PHP_MAX_SALT_LEN + 1];
193 	char *str, *salt_in = NULL;
194 	size_t str_len, salt_in_len = 0;
195 	zend_string *result;
196 
197 	ZEND_PARSE_PARAMETERS_START(2, 2)
198 		Z_PARAM_STRING(str, str_len)
199 		Z_PARAM_STRING(salt_in, salt_in_len)
200 	ZEND_PARSE_PARAMETERS_END();
201 
202 	salt[0] = salt[PHP_MAX_SALT_LEN] = '\0';
203 
204 	/* This will produce suitable results if people depend on DES-encryption
205 	 * available (passing always 2-character salt). At least for glibc6.1 */
206 	memset(&salt[1], '$', PHP_MAX_SALT_LEN - 1);
207 	memcpy(salt, salt_in, MIN(PHP_MAX_SALT_LEN, salt_in_len));
208 
209 	salt_in_len = MIN(PHP_MAX_SALT_LEN, salt_in_len);
210 	salt[salt_in_len] = '\0';
211 
212 	if ((result = php_crypt(str, (int)str_len, salt, (int)salt_in_len, 0)) == NULL) {
213 		if (salt[0] == '*' && salt[1] == '0') {
214 			RETURN_STRING("*1");
215 		} else {
216 			RETURN_STRING("*0");
217 		}
218 	}
219 	RETURN_STR(result);
220 }
221 /* }}} */
222