xref: /php-src/ext/standard/crypt.c (revision 4dc0b40f)
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 #define IS_VALID_SALT_CHARACTER(c) (((c) >= '.' && (c) <= '9') || ((c) >= 'A' && (c) <= 'Z') || ((c) >= 'a' && (c) <= 'z'))
49 
PHP_MINIT_FUNCTION(crypt)50 PHP_MINIT_FUNCTION(crypt) /* {{{ */
51 {
52 #if PHP_USE_PHP_CRYPT_R
53 	php_init_crypt_r();
54 #endif
55 
56 	return SUCCESS;
57 }
58 /* }}} */
59 
PHP_MSHUTDOWN_FUNCTION(crypt)60 PHP_MSHUTDOWN_FUNCTION(crypt) /* {{{ */
61 {
62 #if PHP_USE_PHP_CRYPT_R
63 	php_shutdown_crypt_r();
64 #endif
65 
66 	return SUCCESS;
67 }
68 /* }}} */
69 
php_crypt(const char * password,const int pass_len,const char * salt,int salt_len,bool quiet)70 PHPAPI zend_string *php_crypt(const char *password, const int pass_len, const char *salt, int salt_len, bool quiet)
71 {
72 	char *crypt_res;
73 	zend_string *result;
74 
75 	if (salt[0] == '*' && (salt[1] == '0' || salt[1] == '1')) {
76 		return NULL;
77 	}
78 
79 /* Windows (win32/crypt) has a stripped down version of libxcrypt and
80 	a CryptoApi md5_crypt implementation */
81 #if PHP_USE_PHP_CRYPT_R
82 	{
83 		struct php_crypt_extended_data buffer;
84 
85 		if (salt[0]=='$' && salt[1]=='1' && salt[2]=='$') {
86 			char output[MD5_HASH_MAX_LEN], *out;
87 
88 			out = php_md5_crypt_r(password, salt, output);
89 			if (out) {
90 				return zend_string_init(out, strlen(out), 0);
91 			}
92 			return NULL;
93 		} else if (salt[0]=='$' && salt[1]=='6' && salt[2]=='$') {
94 			char *output;
95 			output = emalloc(PHP_MAX_SALT_LEN);
96 
97 			crypt_res = php_sha512_crypt_r(password, salt, output, PHP_MAX_SALT_LEN);
98 			if (!crypt_res) {
99 				ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN);
100 				efree(output);
101 				return NULL;
102 			} else {
103 				result = zend_string_init(output, strlen(output), 0);
104 				ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN);
105 				efree(output);
106 				return result;
107 			}
108 		} else if (salt[0]=='$' && salt[1]=='5' && salt[2]=='$') {
109 			char *output;
110 			output = emalloc(PHP_MAX_SALT_LEN);
111 
112 			crypt_res = php_sha256_crypt_r(password, salt, output, PHP_MAX_SALT_LEN);
113 			if (!crypt_res) {
114 				ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN);
115 				efree(output);
116 				return NULL;
117 			} else {
118 				result = zend_string_init(output, strlen(output), 0);
119 				ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN);
120 				efree(output);
121 				return result;
122 			}
123 		} else if (
124 				salt[0] == '$' &&
125 				salt[1] == '2' &&
126 				salt[2] != 0 &&
127 				salt[3] == '$') {
128 			char output[PHP_MAX_SALT_LEN + 1];
129 
130 			memset(output, 0, PHP_MAX_SALT_LEN + 1);
131 
132 			crypt_res = php_crypt_blowfish_rn(password, salt, output, sizeof(output));
133 			if (!crypt_res) {
134 				ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN + 1);
135 				return NULL;
136 			} else {
137 				result = zend_string_init(output, strlen(output), 0);
138 				ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN + 1);
139 				return result;
140 			}
141 		} else if (salt[0] == '_'
142 				|| (IS_VALID_SALT_CHARACTER(salt[0]) && IS_VALID_SALT_CHARACTER(salt[1]))) {
143 			/* DES Fallback */
144 			memset(&buffer, 0, sizeof(buffer));
145 			_crypt_extended_init_r();
146 
147 			crypt_res = _crypt_extended_r((const unsigned char *) password, salt, &buffer);
148 			if (!crypt_res || (salt[0] == '*' && salt[1] == '0')) {
149 				return NULL;
150 			} else {
151 				result = zend_string_init(crypt_res, strlen(crypt_res), 0);
152 				return result;
153 			}
154 		} else {
155 			/* Unknown hash type */
156 			return NULL;
157 		}
158 	}
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 	if (!crypt_res || (salt[0] == '*' && salt[1] == '0')) {
179 		return NULL;
180 	}
181 	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 		return NULL;
191 	}
192 	else {
193 		result = zend_string_init(crypt_res, strlen(crypt_res), 0);
194 		return result;
195 	}
196 }
197 /* }}} */
198 
199 
200 /* {{{ Hash a string */
PHP_FUNCTION(crypt)201 PHP_FUNCTION(crypt)
202 {
203 	char salt[PHP_MAX_SALT_LEN + 1];
204 	char *str, *salt_in = NULL;
205 	size_t str_len, salt_in_len = 0;
206 	zend_string *result;
207 
208 	ZEND_PARSE_PARAMETERS_START(2, 2)
209 		Z_PARAM_STRING(str, str_len)
210 		Z_PARAM_STRING(salt_in, salt_in_len)
211 	ZEND_PARSE_PARAMETERS_END();
212 
213 	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 	memset(&salt[1], '$', PHP_MAX_SALT_LEN - 1);
218 	memcpy(salt, salt_in, MIN(PHP_MAX_SALT_LEN, salt_in_len));
219 
220 	salt_in_len = MIN(PHP_MAX_SALT_LEN, salt_in_len);
221 	salt[salt_in_len] = '\0';
222 
223 	if ((result = php_crypt(str, (int)str_len, salt, (int)salt_in_len, 0)) == NULL) {
224 		if (salt[0] == '*' && salt[1] == '0') {
225 			RETURN_STRING("*1");
226 		} else {
227 			RETURN_STRING("*0");
228 		}
229 	}
230 	RETURN_STR(result);
231 }
232 /* }}} */
233