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