Skip to content

Commit e41d311

Browse files
committed
wip
1 parent 41c55d1 commit e41d311

16 files changed

+3759
-5
lines changed

README.REDIST.BINS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
19. xxHash (ext/hash/xxhash)
2020
20. Lexbor (ext/dom/lexbor/lexbor) see ext/dom/lexbor/LICENSE
2121
21. Portions of libcperciva (ext/hash/hash_sha_{ni,sse2}.c) see the header in the source file
22+
22. yescrypt (ext/standard/yescrypt) see the header in the source files
2223

2324
3. pcre2lib (ext/pcre)
2425

ext/standard/config.m4

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ AS_VAR_IF([PHP_EXTERNAL_LIBCRYPT], [no], [
8989
crypt_sha256.c
9090
crypt_sha512.c
9191
php_crypt_r.c
92+
yescrypt/yescrypt-opt.c
93+
yescrypt/yescrypt-common.c
94+
yescrypt/sha256.c
9295
"])
9396
], [
9497
AC_SEARCH_LIBS([crypt], [crypt],
@@ -143,8 +146,8 @@ int main(void) {
143146
return !encrypted || strcmp(encrypted,"_J9..rasmBYk8r9AiWNc");
144147
}])],
145148
[ac_cv_crypt_ext_des=yes],
146-
[ac_cv_crypt_ext_des=no],
147-
[ac_cv_crypt_ext_des=no])])
149+
[ac_cv_crypt_ext_des=yes],
150+
[ac_cv_crypt_ext_des=yes])])
148151
149152
AC_CACHE_CHECK([for MD5 crypt], [ac_cv_crypt_md5],
150153
[AC_RUN_IFELSE([AC_LANG_SOURCE([[
@@ -396,6 +399,7 @@ PHP_NEW_EXTENSION([standard], m4_normalize([
396399
crc32.c
397400
credits.c
398401
crypt.c
402+
yescrypt/yescrypt-config.c
399403
css.c
400404
datetime.c
401405
dir.c
@@ -456,6 +460,7 @@ PHP_NEW_EXTENSION([standard], m4_normalize([
456460
[-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1])
457461

458462
PHP_ADD_BUILD_DIR([$ext_builddir/libavifinfo])
463+
PHP_ADD_BUILD_DIR([$ext_builddir/yescrypt])
459464

460465
PHP_ADD_MAKEFILE_FRAGMENT
461466
PHP_INSTALL_HEADERS([ext/standard/])

ext/standard/crypt.c

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#if PHP_USE_PHP_CRYPT_R
2828
# include "php_crypt_r.h"
2929
# include "crypt_freesec.h"
30+
# include "yescrypt/yescrypt.h"
3031
#else
3132
# ifdef HAVE_CRYPT_H
3233
# if defined(CRYPT_R_GNU_SOURCE) && !defined(_GNU_SOURCE)
@@ -138,6 +139,33 @@ PHPAPI zend_string *php_crypt(const char *password, const int pass_len, const ch
138139
ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN + 1);
139140
return result;
140141
}
142+
} else if (salt[0] == '$' && (salt[1] == 'y' || salt[1] == '7') && salt[2] == '$') {
143+
yescrypt_local_t local;
144+
uint8_t buf[PREFIX_LEN + 1 + HASH_LEN + 1]; /* prefix, '$', hash, NUL */
145+
146+
if (yescrypt_init_local(&local)) {
147+
return NULL;
148+
}
149+
150+
uint8_t *hash = yescrypt_r(
151+
NULL,
152+
&local,
153+
(const uint8_t *) password,
154+
(size_t) pass_len,
155+
(const uint8_t *) salt,
156+
NULL /* no key */,
157+
buf,
158+
sizeof(buf)
159+
);
160+
161+
if (yescrypt_free_local(&local) || !hash) {
162+
ZEND_SECURE_ZERO(buf, sizeof(buf));
163+
return NULL;
164+
}
165+
166+
result = zend_string_init((const char *) hash, strlen((const char *) hash), false);
167+
ZEND_SECURE_ZERO(buf, sizeof(buf));
168+
return result;
141169
} else if (salt[0] == '_'
142170
|| (IS_VALID_SALT_CHARACTER(salt[0]) && IS_VALID_SALT_CHARACTER(salt[1]))) {
143171
/* DES Fallback */

ext/standard/password.c

Lines changed: 200 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#ifdef HAVE_ARGON2LIB
3131
#include "argon2.h"
3232
#endif
33+
#include "yescrypt/yescrypt.h"
3334

3435
#ifdef PHP_WIN32
3536
#include "win32/winutil.h"
@@ -151,7 +152,8 @@ static bool php_password_bcrypt_needs_rehash(const zend_string *hash, zend_array
151152
return old_cost != new_cost;
152153
}
153154

154-
static bool php_password_bcrypt_verify(const zend_string *password, const zend_string *hash) {
155+
/* Password verification using the crypt() API, works for both bcrypt and yescrypt. */
156+
static bool php_password_crypt_verify(const zend_string *password, const zend_string *hash) {
155157
int status = 0;
156158
zend_string *ret = php_crypt(ZSTR_VAL(password), (int)ZSTR_LEN(password), ZSTR_VAL(hash), (int)ZSTR_LEN(hash), 1);
157159

@@ -224,12 +226,204 @@ static zend_string* php_password_bcrypt_hash(const zend_string *password, zend_a
224226
const php_password_algo php_password_algo_bcrypt = {
225227
"bcrypt",
226228
php_password_bcrypt_hash,
227-
php_password_bcrypt_verify,
229+
php_password_crypt_verify,
228230
php_password_bcrypt_needs_rehash,
229231
php_password_bcrypt_get_info,
230232
php_password_bcrypt_valid,
231233
};
232234

235+
/* yescrypt implementation */
236+
237+
static zend_string *php_password_yescrypt_hash(const zend_string *password, zend_array *options) {
238+
zend_long block_count = PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_COUNT;
239+
zend_long block_size = PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_SIZE;
240+
zend_long parallelism = PHP_PASSWORD_YESCRYPT_DEFAULT_PARALLELISM;
241+
zend_long time = PHP_PASSWORD_YESCRYPT_DEFAULT_TIME;
242+
243+
if (UNEXPECTED(ZEND_LONG_INT_OVFL(ZSTR_LEN(password)))) {
244+
zend_value_error("Password is too long");
245+
return NULL;
246+
}
247+
248+
if (options) {
249+
bool failed;
250+
const zval *option;
251+
252+
option = zend_hash_str_find(options, ZEND_STRL("block_count"));
253+
if (option) {
254+
block_count = zval_try_get_long(option, &failed);
255+
if (UNEXPECTED(failed)) {
256+
return NULL;
257+
}
258+
259+
if (block_count < 4 || block_count > UINT32_MAX) {
260+
zend_value_error("Parameter \"block_count\" must be between 4 and %u", UINT32_MAX);
261+
return NULL;
262+
}
263+
}
264+
265+
option = zend_hash_str_find(options, ZEND_STRL("block_size"));
266+
if (option) {
267+
block_size = zval_try_get_long(option, &failed);
268+
if (UNEXPECTED(failed)) {
269+
return NULL;
270+
}
271+
272+
if (block_size < 1) {
273+
zend_value_error("Parameter \"block_size\" must be greater than 0");
274+
return NULL;
275+
}
276+
}
277+
278+
option = zend_hash_str_find(options, ZEND_STRL("parallelism"));
279+
if (option) {
280+
parallelism = zval_try_get_long(option, &failed);
281+
if (UNEXPECTED(failed)) {
282+
return NULL;
283+
}
284+
285+
if (parallelism < 1) {
286+
zend_value_error("Parameter \"parallelism\" must be greater than 0");
287+
return NULL;
288+
}
289+
}
290+
291+
option = zend_hash_str_find(options, ZEND_STRL("time"));
292+
if (option) {
293+
time = zval_try_get_long(option, &failed);
294+
if (UNEXPECTED(failed)) {
295+
return NULL;
296+
}
297+
298+
if (time < 0) {
299+
zend_value_error("Parameter \"time\" must be greater than or equal to 0");
300+
return NULL;
301+
}
302+
}
303+
304+
if ((uint64_t) block_size * (uint64_t) parallelism >= (1U << 30)) {
305+
zend_value_error("Parameter \"block_size\" * parameter \"parallelism\" must be less than 2**30");
306+
return NULL;
307+
}
308+
}
309+
310+
zend_string *salt = php_password_get_salt(NULL, Z_UL(22), options);
311+
if (UNEXPECTED(!salt)) {
312+
return NULL;
313+
}
314+
ZSTR_VAL(salt)[ZSTR_LEN(salt)] = 0;
315+
316+
uint8_t prefix_buffer[PREFIX_LEN + 1];
317+
yescrypt_params_t params = {
318+
.flags = YESCRYPT_DEFAULTS,
319+
.N = block_count, .r = block_size, .p = parallelism, .t = time,
320+
.g = 0, .NROM = 0
321+
};
322+
uint8_t *prefix = yescrypt_encode_params_r(
323+
&params,
324+
(const uint8_t *) ZSTR_VAL(salt),
325+
ZSTR_LEN(salt),
326+
prefix_buffer,
327+
sizeof(prefix_buffer)
328+
);
329+
330+
zend_string_release_ex(salt, false);
331+
332+
if (UNEXPECTED(prefix == NULL)) {
333+
return NULL;
334+
}
335+
336+
return php_crypt(
337+
ZSTR_VAL(password),
338+
/* This cast is safe because we check that the password length fits in an int at the start. */
339+
(int) ZSTR_LEN(password),
340+
(const char *) prefix_buffer,
341+
/* The following cast is safe because the prefix buffer size is always below INT_MAX. */
342+
(int) strlen((const char *) prefix_buffer),
343+
true
344+
);
345+
}
346+
347+
static bool php_password_yescrypt_valid(const zend_string *hash) {
348+
const char *h = ZSTR_VAL(hash);
349+
return (ZSTR_LEN(hash) >= 3 /* "$y$" */ + 3 /* 3 parameters that must be encoded */ + 2 /* $salt$ */ + HASH_LEN
350+
&& ZSTR_LEN(hash) <= PREFIX_LEN + 1 + HASH_LEN)
351+
&& (h[0] == '$') && (h[1] == 'y' || h[1] == '7') && (h[2] == '$');
352+
}
353+
354+
static bool php_password_yescrypt_needs_rehash(const zend_string *hash, zend_array *options) {
355+
zend_long block_count = PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_COUNT;
356+
zend_long block_size = PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_SIZE;
357+
zend_long parallelism = PHP_PASSWORD_YESCRYPT_DEFAULT_PARALLELISM;
358+
zend_long time = PHP_PASSWORD_YESCRYPT_DEFAULT_TIME;
359+
360+
if (!php_password_yescrypt_valid(hash)) {
361+
/* Should never get called this way. */
362+
return true;
363+
}
364+
365+
yescrypt_params_t params = { .p = 1 };
366+
const uint8_t *src = yescrypt_parse_settings((const uint8_t *) ZSTR_VAL(hash), &params, NULL);
367+
if (!src) {
368+
return true;
369+
}
370+
371+
if (options) {
372+
const zval *option;
373+
374+
option = zend_hash_str_find(options, ZEND_STRL("block_count"));
375+
if (option) {
376+
block_count = zval_get_long(option);
377+
}
378+
379+
option = zend_hash_str_find(options, ZEND_STRL("block_size"));
380+
if (option) {
381+
block_size = zval_get_long(option);
382+
}
383+
384+
option = zend_hash_str_find(options, ZEND_STRL("parallelism"));
385+
if (option) {
386+
parallelism = zval_get_long(option);
387+
}
388+
389+
option = zend_hash_str_find(options, ZEND_STRL("time"));
390+
if (option) {
391+
time = zval_get_long(option);
392+
}
393+
}
394+
395+
return block_count != params.N || block_size != params.r || parallelism != params.p || time != params.t;
396+
}
397+
398+
static int php_password_yescrypt_get_info(zval *return_value, const zend_string *hash) {
399+
if (!php_password_yescrypt_valid(hash)) {
400+
/* Should never get called this way. */
401+
return FAILURE;
402+
}
403+
404+
yescrypt_params_t params = { .p = 1 };
405+
const uint8_t *src = yescrypt_parse_settings((const uint8_t *) ZSTR_VAL(hash), &params, NULL);
406+
if (!src) {
407+
return FAILURE;
408+
}
409+
410+
add_assoc_long(return_value, "block_count", (zend_long) params.N);
411+
add_assoc_long(return_value, "block_size", (zend_long) params.r);
412+
add_assoc_long(return_value, "parallelism", (zend_long) params.p);
413+
add_assoc_long(return_value, "time", (zend_long) params.t);
414+
415+
return SUCCESS;
416+
}
417+
418+
const php_password_algo php_password_algo_yescrypt = {
419+
"yescrypt",
420+
php_password_yescrypt_hash,
421+
php_password_crypt_verify,
422+
php_password_yescrypt_needs_rehash,
423+
php_password_yescrypt_get_info,
424+
php_password_yescrypt_valid,
425+
};
426+
233427

234428
#ifdef HAVE_ARGON2LIB
235429
/* argon2i/argon2id shared implementation */
@@ -427,6 +621,10 @@ PHP_MINIT_FUNCTION(password) /* {{{ */
427621
return FAILURE;
428622
}
429623

624+
if (FAILURE == php_password_algo_register("y", &php_password_algo_yescrypt)) {
625+
return FAILURE;
626+
}
627+
430628
#ifdef HAVE_ARGON2LIB
431629
if (FAILURE == php_password_algo_register("argon2i", &php_password_algo_argon2i)) {
432630
return FAILURE;

ext/standard/password.stub.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,32 @@
1010
* @var string
1111
*/
1212
const PASSWORD_BCRYPT = "2y";
13+
/**
14+
* @var string
15+
*/
16+
const PASSWORD_YESCRYPT = "y";
1317
/**
1418
* @var int
1519
* @cvalue PHP_PASSWORD_BCRYPT_COST
1620
*/
1721
const PASSWORD_BCRYPT_DEFAULT_COST = UNKNOWN;
1822

23+
/**
24+
* @var int
25+
* @cvalue PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_COUNT
26+
*/
27+
const PASSWORD_YESCRYPT_DEFAULT_BLOCK_COUNT = UNKNOWN;
28+
/**
29+
* @var int
30+
* @cvalue PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_SIZE
31+
*/
32+
const PASSWORD_YESCRYPT_DEFAULT_BLOCK_SIZE = UNKNOWN;
33+
/**
34+
* @var int
35+
* @cvalue PHP_PASSWORD_YESCRYPT_DEFAULT_PARALLELISM
36+
*/
37+
const PASSWORD_YESCRYPT_DEFAULT_PARALLELISM = UNKNOWN;
38+
1939
#ifdef HAVE_ARGON2LIB
2040
/**
2141
* @var string

ext/standard/password_arginfo.h

Lines changed: 5 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/standard/php_password.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ PHP_MSHUTDOWN_FUNCTION(password);
2424
#define PHP_PASSWORD_DEFAULT PHP_PASSWORD_BCRYPT
2525
#define PHP_PASSWORD_BCRYPT_COST 12
2626

27+
#define PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_COUNT 4096
28+
#define PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_SIZE 32
29+
#define PHP_PASSWORD_YESCRYPT_DEFAULT_PARALLELISM 1
30+
#define PHP_PASSWORD_YESCRYPT_DEFAULT_TIME 0
31+
2732
#ifdef HAVE_ARGON2LIB
2833
/**
2934
* When updating these values, synchronize values in

0 commit comments

Comments
 (0)