1 /*
2 +----------------------------------------------------------------------+
3 | PHP Version 7 |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 1997-2018 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: Anthony Ferrara <ircmaxell@php.net> |
16 | Charles R. Portwood II <charlesportwoodii@erianna.com> |
17 +----------------------------------------------------------------------+
18 */
19
20 #include <stdlib.h>
21
22 #include "php.h"
23
24 #include "fcntl.h"
25 #include "php_password.h"
26 #include "php_rand.h"
27 #include "php_crypt.h"
28 #include "base64.h"
29 #include "zend_interfaces.h"
30 #include "info.h"
31 #include "php_random.h"
32 #if HAVE_ARGON2LIB
33 #include "argon2.h"
34 #endif
35
36 #ifdef PHP_WIN32
37 #include "win32/winutil.h"
38 #endif
39
PHP_MINIT_FUNCTION(password)40 PHP_MINIT_FUNCTION(password) /* {{{ */
41 {
42 REGISTER_LONG_CONSTANT("PASSWORD_DEFAULT", PHP_PASSWORD_DEFAULT, CONST_CS | CONST_PERSISTENT);
43 REGISTER_LONG_CONSTANT("PASSWORD_BCRYPT", PHP_PASSWORD_BCRYPT, CONST_CS | CONST_PERSISTENT);
44 #if HAVE_ARGON2LIB
45 REGISTER_LONG_CONSTANT("PASSWORD_ARGON2I", PHP_PASSWORD_ARGON2I, CONST_CS | CONST_PERSISTENT);
46 REGISTER_LONG_CONSTANT("PASSWORD_ARGON2ID", PHP_PASSWORD_ARGON2ID, CONST_CS | CONST_PERSISTENT);
47 #endif
48
49 REGISTER_LONG_CONSTANT("PASSWORD_BCRYPT_DEFAULT_COST", PHP_PASSWORD_BCRYPT_COST, CONST_CS | CONST_PERSISTENT);
50 #if HAVE_ARGON2LIB
51 REGISTER_LONG_CONSTANT("PASSWORD_ARGON2_DEFAULT_MEMORY_COST", PHP_PASSWORD_ARGON2_MEMORY_COST, CONST_CS | CONST_PERSISTENT);
52 REGISTER_LONG_CONSTANT("PASSWORD_ARGON2_DEFAULT_TIME_COST", PHP_PASSWORD_ARGON2_TIME_COST, CONST_CS | CONST_PERSISTENT);
53 REGISTER_LONG_CONSTANT("PASSWORD_ARGON2_DEFAULT_THREADS", PHP_PASSWORD_ARGON2_THREADS, CONST_CS | CONST_PERSISTENT);
54 #endif
55
56 return SUCCESS;
57 }
58 /* }}} */
59
php_password_get_algo_name(const php_password_algo algo)60 static zend_string* php_password_get_algo_name(const php_password_algo algo)
61 {
62 switch (algo) {
63 case PHP_PASSWORD_BCRYPT:
64 return zend_string_init("bcrypt", sizeof("bcrypt") - 1, 0);
65 #if HAVE_ARGON2LIB
66 case PHP_PASSWORD_ARGON2I:
67 return zend_string_init("argon2i", sizeof("argon2i") - 1, 0);
68 case PHP_PASSWORD_ARGON2ID:
69 return zend_string_init("argon2id", sizeof("argon2id") - 1, 0);
70 #endif
71 case PHP_PASSWORD_UNKNOWN:
72 default:
73 return zend_string_init("unknown", sizeof("unknown") - 1, 0);
74 }
75 }
76
php_password_determine_algo(const zend_string * hash)77 static php_password_algo php_password_determine_algo(const zend_string *hash)
78 {
79 const char *h = ZSTR_VAL(hash);
80 const size_t len = ZSTR_LEN(hash);
81 if (len == 60 && h[0] == '$' && h[1] == '2' && h[2] == 'y') {
82 return PHP_PASSWORD_BCRYPT;
83 }
84 #if HAVE_ARGON2LIB
85 if (len >= sizeof("$argon2id$")-1 && !memcmp(h, "$argon2id$", sizeof("$argon2id$")-1)) {
86 return PHP_PASSWORD_ARGON2ID;
87 }
88
89 if (len >= sizeof("$argon2i$")-1 && !memcmp(h, "$argon2i$", sizeof("$argon2i$")-1)) {
90 return PHP_PASSWORD_ARGON2I;
91 }
92 #endif
93
94 return PHP_PASSWORD_UNKNOWN;
95 }
96
php_password_salt_is_alphabet(const char * str,const size_t len)97 static int php_password_salt_is_alphabet(const char *str, const size_t len) /* {{{ */
98 {
99 size_t i = 0;
100
101 for (i = 0; i < len; i++) {
102 if (!((str[i] >= 'A' && str[i] <= 'Z') || (str[i] >= 'a' && str[i] <= 'z') || (str[i] >= '0' && str[i] <= '9') || str[i] == '.' || str[i] == '/')) {
103 return FAILURE;
104 }
105 }
106 return SUCCESS;
107 }
108 /* }}} */
109
php_password_salt_to64(const char * str,const size_t str_len,const size_t out_len,char * ret)110 static int php_password_salt_to64(const char *str, const size_t str_len, const size_t out_len, char *ret) /* {{{ */
111 {
112 size_t pos = 0;
113 zend_string *buffer;
114 if ((int) str_len < 0) {
115 return FAILURE;
116 }
117 buffer = php_base64_encode((unsigned char*) str, str_len);
118 if (ZSTR_LEN(buffer) < out_len) {
119 /* Too short of an encoded string generated */
120 zend_string_release_ex(buffer, 0);
121 return FAILURE;
122 }
123 for (pos = 0; pos < out_len; pos++) {
124 if (ZSTR_VAL(buffer)[pos] == '+') {
125 ret[pos] = '.';
126 } else if (ZSTR_VAL(buffer)[pos] == '=') {
127 zend_string_free(buffer);
128 return FAILURE;
129 } else {
130 ret[pos] = ZSTR_VAL(buffer)[pos];
131 }
132 }
133 zend_string_free(buffer);
134 return SUCCESS;
135 }
136 /* }}} */
137
php_password_make_salt(size_t length)138 static zend_string* php_password_make_salt(size_t length) /* {{{ */
139 {
140 zend_string *ret, *buffer;
141
142 if (length > (INT_MAX / 3)) {
143 php_error_docref(NULL, E_WARNING, "Length is too large to safely generate");
144 return NULL;
145 }
146
147 buffer = zend_string_alloc(length * 3 / 4 + 1, 0);
148 if (FAILURE == php_random_bytes_silent(ZSTR_VAL(buffer), ZSTR_LEN(buffer))) {
149 php_error_docref(NULL, E_WARNING, "Unable to generate salt");
150 zend_string_release_ex(buffer, 0);
151 return NULL;
152 }
153
154 ret = zend_string_alloc(length, 0);
155 if (php_password_salt_to64(ZSTR_VAL(buffer), ZSTR_LEN(buffer), length, ZSTR_VAL(ret)) == FAILURE) {
156 php_error_docref(NULL, E_WARNING, "Generated salt too short");
157 zend_string_release_ex(buffer, 0);
158 zend_string_release_ex(ret, 0);
159 return NULL;
160 }
161 zend_string_release_ex(buffer, 0);
162 ZSTR_VAL(ret)[length] = 0;
163 return ret;
164 }
165 /* }}} */
166
167 #if HAVE_ARGON2LIB
extract_argon2_parameters(const php_password_algo algo,const zend_string * hash,zend_long * v,zend_long * memory_cost,zend_long * time_cost,zend_long * threads)168 static void extract_argon2_parameters(const php_password_algo algo, const zend_string *hash,
169 zend_long *v, zend_long *memory_cost,
170 zend_long *time_cost, zend_long *threads) /* {{{ */
171 {
172 if (algo == PHP_PASSWORD_ARGON2ID) {
173 sscanf(ZSTR_VAL(hash), "$%*[argon2id]$v=" ZEND_LONG_FMT "$m=" ZEND_LONG_FMT ",t=" ZEND_LONG_FMT ",p=" ZEND_LONG_FMT, v, memory_cost, time_cost, threads);
174 } else if (algo == PHP_PASSWORD_ARGON2I) {
175 sscanf(ZSTR_VAL(hash), "$%*[argon2i]$v=" ZEND_LONG_FMT "$m=" ZEND_LONG_FMT ",t=" ZEND_LONG_FMT ",p=" ZEND_LONG_FMT, v, memory_cost, time_cost, threads);
176 }
177
178 return;
179 }
180 #endif
181
182 /* {{{ proto array password_get_info(string $hash)
183 Retrieves information about a given hash */
PHP_FUNCTION(password_get_info)184 PHP_FUNCTION(password_get_info)
185 {
186 php_password_algo algo;
187 zend_string *hash, *algo_name;
188 zval options;
189
190 ZEND_PARSE_PARAMETERS_START(1, 1)
191 Z_PARAM_STR(hash)
192 ZEND_PARSE_PARAMETERS_END();
193
194 array_init(&options);
195
196 algo = php_password_determine_algo(hash);
197 algo_name = php_password_get_algo_name(algo);
198
199 switch (algo) {
200 case PHP_PASSWORD_BCRYPT:
201 {
202 zend_long cost = PHP_PASSWORD_BCRYPT_COST;
203 sscanf(ZSTR_VAL(hash), "$2y$" ZEND_LONG_FMT "$", &cost);
204 add_assoc_long(&options, "cost", cost);
205 }
206 break;
207 #if HAVE_ARGON2LIB
208 case PHP_PASSWORD_ARGON2I:
209 case PHP_PASSWORD_ARGON2ID:
210 {
211 zend_long v = 0;
212 zend_long memory_cost = PHP_PASSWORD_ARGON2_MEMORY_COST;
213 zend_long time_cost = PHP_PASSWORD_ARGON2_TIME_COST;
214 zend_long threads = PHP_PASSWORD_ARGON2_THREADS;
215
216 extract_argon2_parameters(algo, hash, &v, &memory_cost, &time_cost, &threads);
217
218 add_assoc_long(&options, "memory_cost", memory_cost);
219 add_assoc_long(&options, "time_cost", time_cost);
220 add_assoc_long(&options, "threads", threads);
221 }
222 break;
223 #endif
224 case PHP_PASSWORD_UNKNOWN:
225 default:
226 break;
227 }
228
229 array_init(return_value);
230
231 add_assoc_long(return_value, "algo", algo);
232 add_assoc_str(return_value, "algoName", algo_name);
233 add_assoc_zval(return_value, "options", &options);
234 }
235 /** }}} */
236
237 /* {{{ proto bool password_needs_rehash(string $hash, int $algo[, array $options])
238 Determines if a given hash requires re-hashing based upon parameters */
PHP_FUNCTION(password_needs_rehash)239 PHP_FUNCTION(password_needs_rehash)
240 {
241 zend_long new_algo = 0;
242 php_password_algo algo;
243 zend_string *hash;
244 HashTable *options = 0;
245 zval *option_buffer;
246
247 ZEND_PARSE_PARAMETERS_START(2, 3)
248 Z_PARAM_STR(hash)
249 Z_PARAM_LONG(new_algo)
250 Z_PARAM_OPTIONAL
251 Z_PARAM_ARRAY_OR_OBJECT_HT(options)
252 ZEND_PARSE_PARAMETERS_END();
253
254 algo = php_password_determine_algo(hash);
255
256 if ((zend_long)algo != new_algo) {
257 RETURN_TRUE;
258 }
259
260 switch (algo) {
261 case PHP_PASSWORD_BCRYPT:
262 {
263 zend_long new_cost = PHP_PASSWORD_BCRYPT_COST, cost = 0;
264
265 if (options && (option_buffer = zend_hash_str_find(options, "cost", sizeof("cost")-1)) != NULL) {
266 new_cost = zval_get_long(option_buffer);
267 }
268
269 sscanf(ZSTR_VAL(hash), "$2y$" ZEND_LONG_FMT "$", &cost);
270 if (cost != new_cost) {
271 RETURN_TRUE;
272 }
273 }
274 break;
275 #if HAVE_ARGON2LIB
276 case PHP_PASSWORD_ARGON2I:
277 case PHP_PASSWORD_ARGON2ID:
278 {
279 zend_long v = 0;
280 zend_long new_memory_cost = PHP_PASSWORD_ARGON2_MEMORY_COST, memory_cost = 0;
281 zend_long new_time_cost = PHP_PASSWORD_ARGON2_TIME_COST, time_cost = 0;
282 zend_long new_threads = PHP_PASSWORD_ARGON2_THREADS, threads = 0;
283
284 if (options && (option_buffer = zend_hash_str_find(options, "memory_cost", sizeof("memory_cost")-1)) != NULL) {
285 new_memory_cost = zval_get_long(option_buffer);
286 }
287
288 if (options && (option_buffer = zend_hash_str_find(options, "time_cost", sizeof("time_cost")-1)) != NULL) {
289 new_time_cost = zval_get_long(option_buffer);
290 }
291
292 if (options && (option_buffer = zend_hash_str_find(options, "threads", sizeof("threads")-1)) != NULL) {
293 new_threads = zval_get_long(option_buffer);
294 }
295
296 extract_argon2_parameters(algo, hash, &v, &memory_cost, &time_cost, &threads);
297
298 if (new_time_cost != time_cost || new_memory_cost != memory_cost || new_threads != threads) {
299 RETURN_TRUE;
300 }
301 }
302 break;
303 #endif
304 case PHP_PASSWORD_UNKNOWN:
305 default:
306 break;
307 }
308 RETURN_FALSE;
309 }
310 /* }}} */
311
312 /* {{{ proto bool password_verify(string password, string hash)
313 Verify a hash created using crypt() or password_hash() */
PHP_FUNCTION(password_verify)314 PHP_FUNCTION(password_verify)
315 {
316 zend_string *password, *hash;
317 php_password_algo algo;
318
319 ZEND_PARSE_PARAMETERS_START(2, 2)
320 Z_PARAM_STR(password)
321 Z_PARAM_STR(hash)
322 ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
323
324 algo = php_password_determine_algo(hash);
325
326 switch(algo) {
327 #if HAVE_ARGON2LIB
328 case PHP_PASSWORD_ARGON2I:
329 case PHP_PASSWORD_ARGON2ID:
330 {
331 argon2_type type;
332 if (algo == PHP_PASSWORD_ARGON2ID) {
333 type = Argon2_id;
334 } else if (algo == PHP_PASSWORD_ARGON2I) {
335 type = Argon2_i;
336 }
337 RETURN_BOOL(ARGON2_OK == argon2_verify(ZSTR_VAL(hash), ZSTR_VAL(password), ZSTR_LEN(password), type));
338 }
339 break;
340 #endif
341 case PHP_PASSWORD_BCRYPT:
342 case PHP_PASSWORD_UNKNOWN:
343 default:
344 {
345 size_t i;
346 int status = 0;
347 zend_string *ret = php_crypt(ZSTR_VAL(password), (int)ZSTR_LEN(password), ZSTR_VAL(hash), (int)ZSTR_LEN(hash), 1);
348
349 if (!ret) {
350 RETURN_FALSE;
351 }
352
353 if (ZSTR_LEN(ret) != ZSTR_LEN(hash) || ZSTR_LEN(hash) < 13) {
354 zend_string_free(ret);
355 RETURN_FALSE;
356 }
357
358 /* We're using this method instead of == in order to provide
359 * resistance towards timing attacks. This is a constant time
360 * equality check that will always check every byte of both
361 * values. */
362 for (i = 0; i < ZSTR_LEN(hash); i++) {
363 status |= (ZSTR_VAL(ret)[i] ^ ZSTR_VAL(hash)[i]);
364 }
365
366 zend_string_free(ret);
367
368 RETURN_BOOL(status == 0);
369 }
370 }
371
372 RETURN_FALSE;
373 }
374 /* }}} */
375
php_password_get_salt(zval * return_value,size_t required_salt_len,HashTable * options)376 static zend_string* php_password_get_salt(zval *return_value, size_t required_salt_len, HashTable *options) {
377 zend_string *buffer;
378 zval *option_buffer;
379
380 if (!options || !(option_buffer = zend_hash_str_find(options, "salt", sizeof("salt") - 1))) {
381 buffer = php_password_make_salt(required_salt_len);
382 if (!buffer) {
383 RETVAL_FALSE;
384 }
385 return buffer;
386 }
387
388 php_error_docref(NULL, E_DEPRECATED, "Use of the 'salt' option to password_hash is deprecated");
389
390 switch (Z_TYPE_P(option_buffer)) {
391 case IS_STRING:
392 buffer = zend_string_copy(Z_STR_P(option_buffer));
393 break;
394 case IS_LONG:
395 case IS_DOUBLE:
396 case IS_OBJECT:
397 buffer = zval_get_string(option_buffer);
398 break;
399 case IS_FALSE:
400 case IS_TRUE:
401 case IS_NULL:
402 case IS_RESOURCE:
403 case IS_ARRAY:
404 default:
405 php_error_docref(NULL, E_WARNING, "Non-string salt parameter supplied");
406 return NULL;
407 }
408
409 /* XXX all the crypt related APIs work with int for string length.
410 That should be revised for size_t and then we maybe don't require
411 the > INT_MAX check. */
412 if (ZEND_SIZE_T_INT_OVFL(ZSTR_LEN(buffer))) {
413 php_error_docref(NULL, E_WARNING, "Supplied salt is too long");
414 zend_string_release_ex(buffer, 0);
415 return NULL;
416 }
417
418 if (ZSTR_LEN(buffer) < required_salt_len) {
419 php_error_docref(NULL, E_WARNING, "Provided salt is too short: %zd expecting %zd", ZSTR_LEN(buffer), required_salt_len);
420 zend_string_release_ex(buffer, 0);
421 return NULL;
422 }
423
424 if (php_password_salt_is_alphabet(ZSTR_VAL(buffer), ZSTR_LEN(buffer)) == FAILURE) {
425 zend_string *salt = zend_string_alloc(required_salt_len, 0);
426 if (php_password_salt_to64(ZSTR_VAL(buffer), ZSTR_LEN(buffer), required_salt_len, ZSTR_VAL(salt)) == FAILURE) {
427 php_error_docref(NULL, E_WARNING, "Provided salt is too short: %zd", ZSTR_LEN(buffer));
428 zend_string_release_ex(salt, 0);
429 zend_string_release_ex(buffer, 0);
430 return NULL;
431 }
432 zend_string_release_ex(buffer, 0);
433 return salt;
434 } else {
435 zend_string *salt = zend_string_alloc(required_salt_len, 0);
436 memcpy(ZSTR_VAL(salt), ZSTR_VAL(buffer), required_salt_len);
437 zend_string_release_ex(buffer, 0);
438 return salt;
439 }
440 }
441
442 /* {{{ proto string password_hash(string password, int algo[, array options = array()])
443 Hash a password */
PHP_FUNCTION(password_hash)444 PHP_FUNCTION(password_hash)
445 {
446 zend_string *password;
447 zend_long algo = PHP_PASSWORD_DEFAULT;
448 HashTable *options = NULL;
449
450 ZEND_PARSE_PARAMETERS_START(2, 3)
451 Z_PARAM_STR(password)
452 Z_PARAM_LONG(algo)
453 Z_PARAM_OPTIONAL
454 Z_PARAM_ARRAY_OR_OBJECT_HT(options)
455 ZEND_PARSE_PARAMETERS_END();
456
457 switch (algo) {
458 case PHP_PASSWORD_BCRYPT:
459 {
460 char hash_format[10];
461 size_t hash_format_len;
462 zend_string *result, *hash, *salt;
463 zval *option_buffer;
464 zend_long cost = PHP_PASSWORD_BCRYPT_COST;
465
466 if (options && (option_buffer = zend_hash_str_find(options, "cost", sizeof("cost")-1)) != NULL) {
467 cost = zval_get_long(option_buffer);
468 }
469
470 if (cost < 4 || cost > 31) {
471 php_error_docref(NULL, E_WARNING, "Invalid bcrypt cost parameter specified: " ZEND_LONG_FMT, cost);
472 RETURN_NULL();
473 }
474
475 hash_format_len = snprintf(hash_format, sizeof(hash_format), "$2y$%02" ZEND_LONG_FMT_SPEC "$", cost);
476 if (!(salt = php_password_get_salt(return_value, Z_UL(22), options))) {
477 return;
478 }
479 ZSTR_VAL(salt)[ZSTR_LEN(salt)] = 0;
480
481 hash = zend_string_alloc(ZSTR_LEN(salt) + hash_format_len, 0);
482 sprintf(ZSTR_VAL(hash), "%s%s", hash_format, ZSTR_VAL(salt));
483 ZSTR_VAL(hash)[hash_format_len + ZSTR_LEN(salt)] = 0;
484
485 zend_string_release_ex(salt, 0);
486
487 /* This cast is safe, since both values are defined here in code and cannot overflow */
488 result = php_crypt(ZSTR_VAL(password), (int)ZSTR_LEN(password), ZSTR_VAL(hash), (int)ZSTR_LEN(hash), 1);
489 zend_string_release_ex(hash, 0);
490
491 if (!result) {
492 RETURN_FALSE;
493 }
494
495 if (ZSTR_LEN(result) < 13) {
496 zend_string_free(result);
497 RETURN_FALSE;
498 }
499
500 RETURN_STR(result);
501 }
502 break;
503 #if HAVE_ARGON2LIB
504 case PHP_PASSWORD_ARGON2I:
505 case PHP_PASSWORD_ARGON2ID:
506 {
507 zval *option_buffer;
508 zend_string *salt, *out, *encoded;
509 size_t time_cost = PHP_PASSWORD_ARGON2_TIME_COST;
510 size_t memory_cost = PHP_PASSWORD_ARGON2_MEMORY_COST;
511 size_t threads = PHP_PASSWORD_ARGON2_THREADS;
512 argon2_type type;
513 if (algo == PHP_PASSWORD_ARGON2ID) {
514 type = Argon2_id;
515 } else if (algo == PHP_PASSWORD_ARGON2I) {
516 type = Argon2_i;
517 }
518 size_t encoded_len;
519 int status = 0;
520
521 if (options && (option_buffer = zend_hash_str_find(options, "memory_cost", sizeof("memory_cost")-1)) != NULL) {
522 memory_cost = zval_get_long(option_buffer);
523 }
524
525 if (memory_cost > ARGON2_MAX_MEMORY || memory_cost < ARGON2_MIN_MEMORY) {
526 php_error_docref(NULL, E_WARNING, "Memory cost is outside of allowed memory range");
527 RETURN_NULL();
528 }
529
530 if (options && (option_buffer = zend_hash_str_find(options, "time_cost", sizeof("time_cost")-1)) != NULL) {
531 time_cost = zval_get_long(option_buffer);
532 }
533
534 if (time_cost > ARGON2_MAX_TIME || time_cost < ARGON2_MIN_TIME) {
535 php_error_docref(NULL, E_WARNING, "Time cost is outside of allowed time range");
536 RETURN_NULL();
537 }
538
539 if (options && (option_buffer = zend_hash_str_find(options, "threads", sizeof("threads")-1)) != NULL) {
540 threads = zval_get_long(option_buffer);
541 }
542
543 if (threads > ARGON2_MAX_LANES || threads == 0) {
544 php_error_docref(NULL, E_WARNING, "Invalid number of threads");
545 RETURN_NULL();
546 }
547
548 if (!(salt = php_password_get_salt(return_value, Z_UL(16), options))) {
549 return;
550 }
551
552 out = zend_string_alloc(32, 0);
553 encoded_len = argon2_encodedlen(
554 time_cost,
555 memory_cost,
556 threads,
557 (uint32_t)ZSTR_LEN(salt),
558 ZSTR_LEN(out),
559 type
560 );
561
562 encoded = zend_string_alloc(encoded_len - 1, 0);
563 status = argon2_hash(
564 time_cost,
565 memory_cost,
566 threads,
567 ZSTR_VAL(password),
568 ZSTR_LEN(password),
569 ZSTR_VAL(salt),
570 ZSTR_LEN(salt),
571 ZSTR_VAL(out),
572 ZSTR_LEN(out),
573 ZSTR_VAL(encoded),
574 encoded_len,
575 type,
576 ARGON2_VERSION_NUMBER
577 );
578
579 zend_string_release_ex(out, 0);
580 zend_string_release_ex(salt, 0);
581
582 if (status != ARGON2_OK) {
583 zend_string_efree(encoded);
584 php_error_docref(NULL, E_WARNING, "%s", argon2_error_message(status));
585 RETURN_FALSE;
586 }
587
588 ZSTR_VAL(encoded)[ZSTR_LEN(encoded)] = 0;
589 RETURN_NEW_STR(encoded);
590 }
591 break;
592 #endif
593 case PHP_PASSWORD_UNKNOWN:
594 default:
595 php_error_docref(NULL, E_WARNING, "Unknown password hashing algorithm: " ZEND_LONG_FMT, algo);
596 RETURN_NULL();
597 }
598 }
599 /* }}} */
600
601 /*
602 * Local variables:
603 * tab-width: 4
604 * c-basic-offset: 4
605 * End:
606 * vim600: sw=4 ts=4 fdm=marker
607 * vim<600: sw=4 ts=4
608 */
609