xref: /PHP-7.4/ext/standard/php_crypt_r.c (revision 588db7ce)
1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 7                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) The PHP Group                                          |
6    +----------------------------------------------------------------------+
7    | This source file is subject to version 3.01 of the PHP license,      |
8    | that is bundled with this package in the file LICENSE, and is        |
9    | available through the world-wide-web at the following url:           |
10    | http://www.php.net/license/3_01.txt                                  |
11    | If you did not receive a copy of the PHP license and are unable to   |
12    | obtain it through the world-wide-web, please send a note to          |
13    | license@php.net so we can mail you a copy immediately.               |
14    +----------------------------------------------------------------------+
15    | Authors: Pierre Alain Joye  <pajoye@php.net                          |
16    +----------------------------------------------------------------------+
17  */
18 
19 /*
20  * License for the Unix md5crypt implementation (md5_crypt):
21  *
22  * ----------------------------------------------------------------------------
23  * "THE BEER-WARE LICENSE" (Revision 42):
24  * <phk@login.dknet.dk> wrote this file.  As long as you retain this notice you
25  * can do whatever you want with this stuff. If we meet some day, and you think
26  * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
27  * ----------------------------------------------------------------------------
28  *
29  * from FreeBSD: crypt.c,v 1.5 1996/10/14 08:34:02 phk Exp
30  * via OpenBSD: md5crypt.c,v 1.9 1997/07/23 20:58:27 kstailey Exp
31  * via NetBSD: md5crypt.c,v 1.4.2.1 2002/01/22 19:31:59 he Exp
32  *
33  */
34 
35 #include "php.h"
36 
37 #include <string.h>
38 
39 #ifdef PHP_WIN32
40 # include <windows.h>
41 # include <Wincrypt.h>
42 #endif
43 
44 #ifdef HAVE_ATOMIC_H /* Solaris 10 defines atomic API within */
45 # include <atomic.h>
46 #else
47 # include <signal.h>
48 #endif
49 #include "php_crypt_r.h"
50 #include "crypt_freesec.h"
51 
52 #if !PHP_WIN32
53 #include "ext/standard/md5.h"
54 #endif
55 
56 #ifdef ZTS
57 MUTEX_T php_crypt_extended_init_lock;
58 #endif
59 
60 /* TODO: enable it when enabling vista/2k8 mode in tsrm */
61 #if 0
62 CONDITION_VARIABLE initialized;
63 #endif
64 
php_init_crypt_r()65 void php_init_crypt_r()
66 {
67 #ifdef ZTS
68 	php_crypt_extended_init_lock = tsrm_mutex_alloc();
69 #endif
70 }
71 
php_shutdown_crypt_r()72 void php_shutdown_crypt_r()
73 {
74 #ifdef ZTS
75 	tsrm_mutex_free(php_crypt_extended_init_lock);
76 #endif
77 }
78 
_crypt_extended_init_r(void)79 void _crypt_extended_init_r(void)
80 {
81 #ifdef PHP_WIN32
82 	LONG volatile initialized = 0;
83 #elif defined(HAVE_ATOMIC_H) /* Solaris 10 defines atomic API within */
84 	volatile unsigned int initialized = 0;
85 #else
86 	static volatile sig_atomic_t initialized = 0;
87 #endif
88 
89 #ifdef ZTS
90 	tsrm_mutex_lock(php_crypt_extended_init_lock);
91 #endif
92 
93 	if (!initialized) {
94 #ifdef PHP_WIN32
95 		InterlockedIncrement(&initialized);
96 #elif defined(HAVE_SYNC_FETCH_AND_ADD)
97 		__sync_fetch_and_add(&initialized, 1);
98 #elif defined(HAVE_ATOMIC_H) /* Solaris 10 defines atomic API within */
99 		membar_producer();
100 		atomic_add_int(&initialized, 1);
101 #endif
102 		_crypt_extended_init();
103 	}
104 #ifdef ZTS
105 	tsrm_mutex_unlock(php_crypt_extended_init_lock);
106 #endif
107 }
108 
109 /* MD% crypt implementation using the windows CryptoApi */
110 #define MD5_MAGIC "$1$"
111 #define MD5_MAGIC_LEN 3
112 
113 static unsigned char itoa64[] =		/* 0 ... 63 => ascii - 64 */
114 	"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
115 
116 static void
to64(char * s,int32_t v,int n)117 to64(char *s, int32_t v, int n)
118 {
119 	while (--n >= 0) {
120 		*s++ = itoa64[v & 0x3f];
121 		v >>= 6;
122 	}
123 }
124 
125 #ifdef PHP_WIN32
php_md5_crypt_r(const char * pw,const char * salt,char * out)126 char * php_md5_crypt_r(const char *pw, const char *salt, char *out) {
127 	HCRYPTPROV hCryptProv;
128 	HCRYPTHASH ctx, ctx1;
129 	DWORD i, pwl, sl;
130 	const BYTE magic_md5[4] = "$1$";
131 	const DWORD magic_md5_len = 3;
132 	DWORD        dwHashLen;
133 	int pl;
134 	__int32 l;
135 	const char *sp = salt;
136 	const char *ep = salt;
137 	char *p = NULL;
138 	char *passwd = out;
139 	unsigned char final[16];
140 
141 	/* Acquire a cryptographic provider context handle. */
142 	if(!CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
143 		return NULL;
144 	}
145 
146 	pwl = (DWORD) strlen(pw);
147 
148 	/* Refine the salt first */
149 	sp = salt;
150 
151 	/* If it starts with the magic string, then skip that */
152 	if (strncmp(sp, MD5_MAGIC, MD5_MAGIC_LEN) == 0) {
153 		sp += MD5_MAGIC_LEN;
154 	}
155 
156 	/* It stops at the first '$', max 8 chars */
157 	for (ep = sp; *ep != '\0' && *ep != '$' && ep < (sp + 8); ep++);
158 
159 	/* get the length of the true salt */
160 	sl = (DWORD)(ep - sp);
161 
162 	/* Create an empty hash object. */
163 	if(!CryptCreateHash(hCryptProv, CALG_MD5, 0, 0, &ctx)) {
164 		goto _destroyProv;
165 	}
166 
167 	/* The password first, since that is what is most unknown */
168 	if(!CryptHashData(ctx, (BYTE *)pw, pwl, 0)) {
169 		goto _destroyCtx0;
170 	}
171 
172 	/* Then our magic string */
173 	if(!CryptHashData(ctx, magic_md5, magic_md5_len, 0)) {
174 		goto _destroyCtx0;
175 	}
176 
177 	/* Then the raw salt */
178 	if(!CryptHashData( ctx, (BYTE *)sp, sl, 0)) {
179 		goto _destroyCtx0;
180 	}
181 
182 	/* MD5(pw,salt,pw), valid. */
183 	/* Then just as many characters of the MD5(pw,salt,pw) */
184 	if(!CryptCreateHash(hCryptProv, CALG_MD5, 0, 0, &ctx1)) {
185 		goto _destroyCtx0;
186 	}
187 	if(!CryptHashData(ctx1, (BYTE *)pw, pwl, 0)) {
188 		goto _destroyCtx1;
189 	}
190 	if(!CryptHashData(ctx1, (BYTE *)sp, sl, 0)) {
191 		goto _destroyCtx1;
192 	}
193 	if(!CryptHashData(ctx1, (BYTE *)pw, pwl, 0)) {
194 		goto _destroyCtx1;
195 	}
196 
197 	dwHashLen = 16;
198 	CryptGetHashParam(ctx1, HP_HASHVAL, final, &dwHashLen, 0);
199 	/*  MD5(pw,salt,pw). Valid. */
200 
201 	for (pl = pwl; pl > 0; pl -= 16) {
202 		CryptHashData(ctx, final, (DWORD)(pl > 16 ? 16 : pl), 0);
203 	}
204 
205 	/* Don't leave anything around in vm they could use. */
206 	ZEND_SECURE_ZERO(final, sizeof(final));
207 
208 	/* Then something really weird... */
209 	for (i = pwl; i != 0; i >>= 1) {
210 		if ((i & 1) != 0) {
211 			CryptHashData(ctx, (const BYTE *)final, 1, 0);
212 		} else {
213 			CryptHashData(ctx, (const BYTE *)pw, 1, 0);
214 		}
215 	}
216 
217 	memcpy(passwd, MD5_MAGIC, MD5_MAGIC_LEN);
218 
219 	if (strncpy_s(passwd + MD5_MAGIC_LEN, MD5_HASH_MAX_LEN - MD5_MAGIC_LEN, sp, sl + 1) != 0) {
220 		goto _destroyCtx1;
221 	}
222 	passwd[MD5_MAGIC_LEN + sl] = '\0';
223 	strcat_s(passwd, MD5_HASH_MAX_LEN, "$");
224 
225 	dwHashLen = 16;
226 
227 	/* Fetch the ctx hash value */
228 	CryptGetHashParam(ctx, HP_HASHVAL, final, &dwHashLen, 0);
229 
230 	for (i = 0; i < 1000; i++) {
231 		if(!CryptCreateHash(hCryptProv, CALG_MD5, 0, 0, &ctx1)) {
232 			goto _destroyCtx1;
233 		}
234 
235 		if ((i & 1) != 0) {
236 			if(!CryptHashData(ctx1, (BYTE *)pw, pwl, 0)) {
237 				goto _destroyCtx1;
238 			}
239 		} else {
240 			if(!CryptHashData(ctx1, (BYTE *)final, 16, 0)) {
241 				goto _destroyCtx1;
242 			}
243 		}
244 
245 		if ((i % 3) != 0) {
246 			if(!CryptHashData(ctx1, (BYTE *)sp, sl, 0)) {
247 				goto _destroyCtx1;
248 			}
249 		}
250 
251 		if ((i % 7) != 0) {
252 			if(!CryptHashData(ctx1, (BYTE *)pw, pwl, 0)) {
253 				goto _destroyCtx1;
254 			}
255 		}
256 
257 		if ((i & 1) != 0) {
258 			if(!CryptHashData(ctx1, (BYTE *)final, 16, 0)) {
259 				goto _destroyCtx1;
260 			}
261 		} else {
262 			if(!CryptHashData(ctx1, (BYTE *)pw, pwl, 0)) {
263 				goto _destroyCtx1;
264 			}
265 		}
266 
267 		/* Fetch the ctx hash value */
268 		dwHashLen = 16;
269 		CryptGetHashParam(ctx1, HP_HASHVAL, final, &dwHashLen, 0);
270 		if(!(CryptDestroyHash(ctx1))) {
271 			goto _destroyCtx0;
272 		}
273 	}
274 
275 	ctx1 = (HCRYPTHASH) NULL;
276 
277 	p = passwd + sl + MD5_MAGIC_LEN + 1;
278 
279 	l = (final[ 0]<<16) | (final[ 6]<<8) | final[12]; to64(p,l,4); p += 4;
280 	l = (final[ 1]<<16) | (final[ 7]<<8) | final[13]; to64(p,l,4); p += 4;
281 	l = (final[ 2]<<16) | (final[ 8]<<8) | final[14]; to64(p,l,4); p += 4;
282 	l = (final[ 3]<<16) | (final[ 9]<<8) | final[15]; to64(p,l,4); p += 4;
283 	l = (final[ 4]<<16) | (final[10]<<8) | final[ 5]; to64(p,l,4); p += 4;
284 	l = final[11]; to64(p,l,2); p += 2;
285 
286 	*p = '\0';
287 
288 	ZEND_SECURE_ZERO(final, sizeof(final));
289 
290 
291 _destroyCtx1:
292 	if (ctx1) {
293 		if (!CryptDestroyHash(ctx1)) {
294 
295 		}
296 	}
297 
298 _destroyCtx0:
299 	CryptDestroyHash(ctx);
300 
301 _destroyProv:
302 	/* Release the provider handle.*/
303 	if(hCryptProv) {
304 		if(!(CryptReleaseContext(hCryptProv, 0))) {
305 			return NULL;
306 		}
307 	}
308 
309 	return out;
310 }
311 #else
312 
313 /*
314  * MD5 password encryption.
315  */
php_md5_crypt_r(const char * pw,const char * salt,char * out)316 char * php_md5_crypt_r(const char *pw, const char *salt, char *out)
317 {
318 	ZEND_TLS char passwd[MD5_HASH_MAX_LEN], *p;
319 	const char *sp, *ep;
320 	unsigned char final[16];
321 	unsigned int i, sl, pwl;
322 	PHP_MD5_CTX	ctx, ctx1;
323 	uint32_t l;
324 	int pl;
325 
326 	pwl = strlen(pw);
327 
328 	/* Refine the salt first */
329 	sp = salt;
330 
331 	/* If it starts with the magic string, then skip that */
332 	if (strncmp(sp, MD5_MAGIC, MD5_MAGIC_LEN) == 0)
333 		sp += MD5_MAGIC_LEN;
334 
335 	/* It stops at the first '$', max 8 chars */
336 	for (ep = sp; *ep != '\0' && *ep != '$' && ep < (sp + 8); ep++);
337 
338 	/* get the length of the true salt */
339 	sl = ep - sp;
340 
341 	PHP_MD5Init(&ctx);
342 
343 	/* The password first, since that is what is most unknown */
344 	PHP_MD5Update(&ctx, (const unsigned char *)pw, pwl);
345 
346 	/* Then our magic string */
347 	PHP_MD5Update(&ctx, (const unsigned char *)MD5_MAGIC, MD5_MAGIC_LEN);
348 
349 	/* Then the raw salt */
350 	PHP_MD5Update(&ctx, (const unsigned char *)sp, sl);
351 
352 	/* Then just as many characters of the MD5(pw,salt,pw) */
353 	PHP_MD5Init(&ctx1);
354 	PHP_MD5Update(&ctx1, (const unsigned char *)pw, pwl);
355 	PHP_MD5Update(&ctx1, (const unsigned char *)sp, sl);
356 	PHP_MD5Update(&ctx1, (const unsigned char *)pw, pwl);
357 	PHP_MD5Final(final, &ctx1);
358 
359 	for (pl = pwl; pl > 0; pl -= 16)
360 		PHP_MD5Update(&ctx, final, (unsigned int)(pl > 16 ? 16 : pl));
361 
362 	/* Don't leave anything around in vm they could use. */
363 	ZEND_SECURE_ZERO(final, sizeof(final));
364 
365 	/* Then something really weird... */
366 	for (i = pwl; i != 0; i >>= 1)
367 		if ((i & 1) != 0)
368 		    PHP_MD5Update(&ctx, final, 1);
369 		else
370 		    PHP_MD5Update(&ctx, (const unsigned char *)pw, 1);
371 
372 	/* Now make the output string */
373 	memcpy(passwd, MD5_MAGIC, MD5_MAGIC_LEN);
374 	strlcpy(passwd + MD5_MAGIC_LEN, sp, sl + 1);
375 	strcat(passwd, "$");
376 
377 	PHP_MD5Final(final, &ctx);
378 
379 	/*
380 	 * And now, just to make sure things don't run too fast. On a 60 MHz
381 	 * Pentium this takes 34 msec, so you would need 30 seconds to build
382 	 * a 1000 entry dictionary...
383 	 */
384 	for (i = 0; i < 1000; i++) {
385 		PHP_MD5Init(&ctx1);
386 
387 		if ((i & 1) != 0)
388 			PHP_MD5Update(&ctx1, (const unsigned char *)pw, pwl);
389 		else
390 			PHP_MD5Update(&ctx1, final, 16);
391 
392 		if ((i % 3) != 0)
393 			PHP_MD5Update(&ctx1, (const unsigned char *)sp, sl);
394 
395 		if ((i % 7) != 0)
396 			PHP_MD5Update(&ctx1, (const unsigned char *)pw, pwl);
397 
398 		if ((i & 1) != 0)
399 			PHP_MD5Update(&ctx1, final, 16);
400 		else
401 			PHP_MD5Update(&ctx1, (const unsigned char *)pw, pwl);
402 
403 		PHP_MD5Final(final, &ctx1);
404 	}
405 
406 	p = passwd + sl + MD5_MAGIC_LEN + 1;
407 
408 	l = (final[ 0]<<16) | (final[ 6]<<8) | final[12]; to64(p,l,4); p += 4;
409 	l = (final[ 1]<<16) | (final[ 7]<<8) | final[13]; to64(p,l,4); p += 4;
410 	l = (final[ 2]<<16) | (final[ 8]<<8) | final[14]; to64(p,l,4); p += 4;
411 	l = (final[ 3]<<16) | (final[ 9]<<8) | final[15]; to64(p,l,4); p += 4;
412 	l = (final[ 4]<<16) | (final[10]<<8) | final[ 5]; to64(p,l,4); p += 4;
413 	l =		       final[11]		; to64(p,l,2); p += 2;
414 	*p = '\0';
415 
416 	/* Don't leave anything around in vm they could use. */
417 	ZEND_SECURE_ZERO(final, sizeof(final));
418 	return (passwd);
419 }
420 
421 #undef MD5_MAGIC
422 #undef MD5_MAGIC_LEN
423 #endif
424