Skip to content
Permalink
Browse files

RFC: Argon2 Password Hash Enhancements Implementation of Argon2id per…

… RFC https://wiki.php.net/rfc/argon2_password_hash_enhancements

- m4 and Windows configure scripts now forces Argon2 reference library version >= 20161029
- Implementation tested against 20161029 and 20171227 for Argon2id support
- Updates Argon2 ext/standard/password/tests to run tests for both Argon2i and Argon2id
  • Loading branch information...
charlesportwoodii authored and cmb69 committed Jun 15, 2018
1 parent 3f96f01 commit 55277a668409b9d62ac42695934aca64e354869f
@@ -150,6 +150,15 @@ readline:
options has been added to readline_info(). These options are only available
if PHP is linked against libreadline (rather than libedit).

Standard:
. The -–with-password-argon2[=dir] configure argument now provides support for
both Argon2i and Argon2id hashes in the password_hash(), password_verify(),
password_get_info(), and password_needs_rehash() functions. Passwords may be
hashed and verified using the PASSWORD_ARGON2ID constant.
Support for both Argon2i and Argon2id in the password_* functions now requires
PHP be linked against libargon2 reference library >= 20161029.
(RFC: https://wiki.php.net/rfc/argon2_password_hash_enhancements).

========================================
3. Changes in SAPI modules
========================================
@@ -311,6 +320,9 @@ PGSQL:
. Requires Postgres 9.6
- PGSQL_DIAG_SEVERITY_NONLOCALIZED

Standard:
. PASSWORD_ARGON2ID

========================================
11. Changes to INI File Handling
========================================
@@ -434,18 +434,11 @@ if test "$PHP_PASSWORD_ARGON2" != "no"; then
PHP_ADD_LIBRARY_WITH_PATH(argon2, $ARGON2_DIR/$PHP_LIBDIR)
PHP_ADD_INCLUDE($ARGON2_DIR/include)

AC_CHECK_LIB(argon2, argon2_hash, [
LIBS="$LIBS -largon2"
AC_DEFINE(HAVE_ARGON2LIB, 1, [ Define to 1 if you have the <argon2.h> header file ])
], [
AC_MSG_ERROR([Problem with libargon2.(a|so). Please verify that Argon2 header and libaries are installed])
])

AC_CHECK_LIB(argon2, argon2id_hash_raw, [
LIBS="$LIBS -largon2"
AC_DEFINE(HAVE_ARGON2ID, 1, [ Define to 1 if Argon2 library has support for Argon2ID])
AC_DEFINE(HAVE_ARGON2LIB, 1, [ Define to 1 if you have the <argon2.h> header file ])
], [
AC_MSG_RESULT([not found])
AC_MSG_ERROR([Problem with libargon2.(a|so). Please verify that Argon2 header and libaries >= 20161029 are installed])
])
fi

@@ -6,10 +6,10 @@ ARG_WITH("password-argon2", "Argon2 support", "no");
if (PHP_PASSWORD_ARGON2 != "no") {
if (CHECK_LIB("argon2_a.lib;argon2.lib", null, PHP_PASSWORD_ARGON2)
&& CHECK_HEADER_ADD_INCLUDE("argon2.h", "CFLAGS")) {
AC_DEFINE('HAVE_ARGON2LIB', 1);
if (CHECK_FUNC_IN_HEADER("argon2.h", "argon2id_hash_raw", PHP_PHP_BUILD + "\\include", "CFLAGS")) {
AC_DEFINE('HAVE_ARGON2ID', 1);
if (!CHECK_FUNC_IN_HEADER("argon2.h", "argon2id_hash_raw", PHP_PHP_BUILD + "\\include", "CFLAGS")) {
ERROR("Please verify that Argon2 header and libaries >= 20161029 are installed");
}
AC_DEFINE('HAVE_ARGON2LIB', 1);
} else {
WARNING("Argon2 not enabled; libaries and headers not found");
}
@@ -45,6 +45,7 @@ PHP_MINIT_FUNCTION(password) /* {{{ */
REGISTER_LONG_CONSTANT("PASSWORD_BCRYPT", PHP_PASSWORD_BCRYPT, CONST_CS | CONST_PERSISTENT);
#if HAVE_ARGON2LIB
REGISTER_LONG_CONSTANT("PASSWORD_ARGON2I", PHP_PASSWORD_ARGON2I, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("PASSWORD_ARGON2ID", PHP_PASSWORD_ARGON2ID, CONST_CS | CONST_PERSISTENT);
#endif

REGISTER_LONG_CONSTANT("PASSWORD_BCRYPT_DEFAULT_COST", PHP_PASSWORD_BCRYPT_COST, CONST_CS | CONST_PERSISTENT);
@@ -66,6 +67,8 @@ static zend_string* php_password_get_algo_name(const php_password_algo algo)
#if HAVE_ARGON2LIB
case PHP_PASSWORD_ARGON2I:
return zend_string_init("argon2i", sizeof("argon2i") - 1, 0);
case PHP_PASSWORD_ARGON2ID:
return zend_string_init("argon2id", sizeof("argon2id") - 1, 0);
#endif
case PHP_PASSWORD_UNKNOWN:
default:
@@ -81,6 +84,10 @@ static php_password_algo php_password_determine_algo(const zend_string *hash)
return PHP_PASSWORD_BCRYPT;
}
#if HAVE_ARGON2LIB
if (len >= sizeof("$argon2id$")-1 && !memcmp(h, "$argon2id$", sizeof("$argon2id$")-1)) {
return PHP_PASSWORD_ARGON2ID;
}

if (len >= sizeof("$argon2i$")-1 && !memcmp(h, "$argon2i$", sizeof("$argon2i$")-1)) {
return PHP_PASSWORD_ARGON2I;
}
@@ -159,6 +166,21 @@ static zend_string* php_password_make_salt(size_t length) /* {{{ */
}
/* }}} */

#if HAVE_ARGON2LIB
static void 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) /* {{{ */
{
if (algo == PHP_PASSWORD_ARGON2ID) {
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);
} else if (algo == PHP_PASSWORD_ARGON2I) {
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);
}

return;
}
#endif

/* {{{ proto array password_get_info(string $hash)
Retrieves information about a given hash */
PHP_FUNCTION(password_get_info)
@@ -186,13 +208,15 @@ PHP_FUNCTION(password_get_info)
break;
#if HAVE_ARGON2LIB
case PHP_PASSWORD_ARGON2I:
case PHP_PASSWORD_ARGON2ID:
{
zend_long v = 0;
zend_long memory_cost = PHP_PASSWORD_ARGON2_MEMORY_COST;
zend_long time_cost = PHP_PASSWORD_ARGON2_TIME_COST;
zend_long threads = PHP_PASSWORD_ARGON2_THREADS;

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);
extract_argon2_parameters(algo, hash, &v, &memory_cost, &time_cost, &threads);

add_assoc_long(&options, "memory_cost", memory_cost);
add_assoc_long(&options, "time_cost", time_cost);
add_assoc_long(&options, "threads", threads);
@@ -252,6 +276,7 @@ PHP_FUNCTION(password_needs_rehash)
break;
#if HAVE_ARGON2LIB
case PHP_PASSWORD_ARGON2I:
case PHP_PASSWORD_ARGON2ID:
{
zend_long v = 0;
zend_long new_memory_cost = PHP_PASSWORD_ARGON2_MEMORY_COST, memory_cost = 0;
@@ -270,7 +295,7 @@ PHP_FUNCTION(password_needs_rehash)
new_threads = zval_get_long(option_buffer);
}

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);
extract_argon2_parameters(algo, hash, &v, &memory_cost, &time_cost, &threads);

if (new_time_cost != time_cost || new_memory_cost != memory_cost || new_threads != threads) {
RETURN_TRUE;
@@ -303,7 +328,16 @@ PHP_FUNCTION(password_verify)
switch(algo) {
#if HAVE_ARGON2LIB
case PHP_PASSWORD_ARGON2I:
RETURN_BOOL(ARGON2_OK == argon2_verify(ZSTR_VAL(hash), ZSTR_VAL(password), ZSTR_LEN(password), Argon2_i));
case PHP_PASSWORD_ARGON2ID:
{
argon2_type type;
if (algo == PHP_PASSWORD_ARGON2ID) {
type = Argon2_id;
} else if (algo == PHP_PASSWORD_ARGON2I) {
type = Argon2_i;
}
RETURN_BOOL(ARGON2_OK == argon2_verify(ZSTR_VAL(hash), ZSTR_VAL(password), ZSTR_LEN(password), type));
}
break;
#endif
case PHP_PASSWORD_BCRYPT:
@@ -470,13 +504,19 @@ PHP_FUNCTION(password_hash)
break;
#if HAVE_ARGON2LIB
case PHP_PASSWORD_ARGON2I:
case PHP_PASSWORD_ARGON2ID:
{
zval *option_buffer;
zend_string *salt, *out, *encoded;
size_t time_cost = PHP_PASSWORD_ARGON2_TIME_COST;
size_t memory_cost = PHP_PASSWORD_ARGON2_MEMORY_COST;
size_t threads = PHP_PASSWORD_ARGON2_THREADS;
argon2_type type = Argon2_i;
argon2_type type;
if (algo == PHP_PASSWORD_ARGON2ID) {
type = Argon2_id;
} else if (algo == PHP_PASSWORD_ARGON2I) {
type = Argon2_i;
}
size_t encoded_len;
int status = 0;

@@ -485,7 +525,7 @@ PHP_FUNCTION(password_hash)
}

if (memory_cost > ARGON2_MAX_MEMORY || memory_cost < ARGON2_MIN_MEMORY) {
php_error_docref(NULL, E_WARNING, "Memory cost is outside of allowed memory range", memory_cost);
php_error_docref(NULL, E_WARNING, "Memory cost is outside of allowed memory range");
RETURN_NULL();
}

@@ -494,7 +534,7 @@ PHP_FUNCTION(password_hash)
}

if (time_cost > ARGON2_MAX_TIME || time_cost < ARGON2_MIN_TIME) {
php_error_docref(NULL, E_WARNING, "Time cost is outside of allowed time range", time_cost);
php_error_docref(NULL, E_WARNING, "Time cost is outside of allowed time range");
RETURN_NULL();
}

@@ -503,7 +543,7 @@ PHP_FUNCTION(password_hash)
}

if (threads > ARGON2_MAX_LANES || threads == 0) {
php_error_docref(NULL, E_WARNING, "Invalid number of threads", threads);
php_error_docref(NULL, E_WARNING, "Invalid number of threads");
RETURN_NULL();
}

@@ -517,10 +557,8 @@ PHP_FUNCTION(password_hash)
memory_cost,
threads,
(uint32_t)ZSTR_LEN(salt),
ZSTR_LEN(out)
#if HAVE_ARGON2ID
, type
#endif
ZSTR_LEN(out),
type
);

encoded = zend_string_alloc(encoded_len - 1, 0);
@@ -43,6 +43,7 @@ typedef enum {
PHP_PASSWORD_BCRYPT,
#if HAVE_ARGON2LIB
PHP_PASSWORD_ARGON2I,
PHP_PASSWORD_ARGON2ID,
#endif
} php_password_algo;

@@ -1,13 +1,15 @@
--TEST--
Test normal operation of password_get_info() with Argon2
Test normal operation of password_get_info() with Argon2i and Argon2id
--SKIPIF--
<?php
if (!defined('PASSWORD_ARGON2I')) die('skip password_get_info not built with Argon2');
if (!defined('PASSWORD_ARGON2ID')) die('skip password_get_info not built with Argon2');
?>
--FILE--
<?php
var_dump(password_get_info('$argon2i$v=19$m=65536,t=3,p=1$SWhIcG5MT21Pc01PbWdVZw$WagZELICsz7jlqOR2YzoEVTWb2oOX1tYdnhZYXxptbU'));
var_dump(password_get_info('$argon2id$v=19$m=1024,t=2,p=2$Zng1U1RHS0h1aUJjbGhPdA$ajQnG5s01Ws1ad8xv+1qGfXF8mYxxWdyul5rBpomuZQ'));
echo "OK!";
?>
--EXPECT--
@@ -26,4 +28,19 @@ array(3) {
int(1)
}
}
array(3) {
["algo"]=>
int(3)
["algoName"]=>
string(8) "argon2id"
["options"]=>
array(3) {
["memory_cost"]=>
int(1024)
["time_cost"]=>
int(2)
["threads"]=>
int(2)
}
}
OK!
@@ -1,8 +1,9 @@
--TEST--
Test normal operation of password_hash() with argon2
Test normal operation of password_hash() with Argon2i and Argon2id
--SKIPIF--
<?php
if (!defined('PASSWORD_ARGON2I')) die('skip password_hash not built with Argon2');
if (!defined('PASSWORD_ARGON2ID')) die('skip password_hash not built with Argon2');
--FILE--
<?php
@@ -11,8 +12,12 @@ $password = "the password for testing 12345!";
$hash = password_hash($password, PASSWORD_ARGON2I);
var_dump(password_verify($password, $hash));
$hash = password_hash($password, PASSWORD_ARGON2ID);
var_dump(password_verify($password, $hash));
echo "OK!";
?>
--EXPECT--
bool(true)
bool(true)
OK!
@@ -1,14 +1,18 @@
--TEST--
Test error operation of password_hash() with argon2
Test error operation of password_hash() with Argon2i and Argon2id
--SKIPIF--
<?php
if (!defined('PASSWORD_ARGON2I')) die('skip password_hash not built with Argon2');
if (!defined('PASSWORD_ARGON2ID')) die('skip password_hash not built with Argon2');
?>
--FILE--
<?php
var_dump(password_hash('test', PASSWORD_ARGON2I, ['memory_cost' => 0]));
var_dump(password_hash('test', PASSWORD_ARGON2I, ['time_cost' => 0]));
var_dump(password_hash('test', PASSWORD_ARGON2I, ['threads' => 0]));
var_dump(password_hash('test', PASSWORD_ARGON2ID, ['memory_cost' => 0]));
var_dump(password_hash('test', PASSWORD_ARGON2ID, ['time_cost' => 0]));
var_dump(password_hash('test', PASSWORD_ARGON2ID, ['threads' => 0]));
?>
--EXPECTF--
Warning: password_hash(): Memory cost is outside of allowed memory range in %s on line %d
@@ -17,5 +21,14 @@ NULL
Warning: password_hash(): Time cost is outside of allowed time range in %s on line %d
NULL

Warning: password_hash(): Invalid number of threads in %s on line %d
NULL

Warning: password_hash(): Memory cost is outside of allowed memory range in %s on line %d
NULL

Warning: password_hash(): Time cost is outside of allowed time range in %s on line %d
NULL

Warning: password_hash(): Invalid number of threads in %s on line %d
NULL
@@ -1,8 +1,9 @@
--TEST--
Test normal operation of password_needs_rehash() with argon2
Test normal operation of password_needs_rehash() with Argon2i and Argon2id
--SKIPIF--
<?php
if (!defined('PASSWORD_ARGON2I')) die('skip password_needs_rehash not built with Argon2');
if (!defined('PASSWORD_ARGON2ID')) die('skip password_hash not built with Argon2');
?>
--FILE--
<?php
@@ -12,11 +13,21 @@ var_dump(password_needs_rehash($hash, PASSWORD_ARGON2I));
var_dump(password_needs_rehash($hash, PASSWORD_ARGON2I, ['memory_cost' => 1<<17]));
var_dump(password_needs_rehash($hash, PASSWORD_ARGON2I, ['time_cost' => 4]));
var_dump(password_needs_rehash($hash, PASSWORD_ARGON2I, ['threads' => 4]));
$hash = password_hash('test', PASSWORD_ARGON2ID);
var_dump(password_needs_rehash($hash, PASSWORD_ARGON2ID));
var_dump(password_needs_rehash($hash, PASSWORD_ARGON2ID, ['memory_cost' => 1<<17]));
var_dump(password_needs_rehash($hash, PASSWORD_ARGON2ID, ['time_cost' => 4]));
var_dump(password_needs_rehash($hash, PASSWORD_ARGON2ID, ['threads' => 4]));
echo "OK!";
?>
--EXPECT--
bool(false)
bool(true)
bool(true)
bool(true)
OK!
bool(false)
bool(true)
bool(true)
bool(true)
OK!
@@ -1,18 +1,24 @@
--TEST--
Test normal operation of password_verify() with argon2
Test normal operation of password_verify() with Argon2i and Argon2id
--SKIPIF--
<?php
if (!defined('PASSWORD_ARGON2I')) die('skip password_verify not built with Argon2');
if (!defined('PASSWORD_ARGON2ID')) die('skip password_hash not built with Argon2');
?>
--FILE--
<?php
var_dump(password_verify('test', '$argon2i$v=19$m=65536,t=3,p=1$OEVjWWs2Z3YvWlNZQ0ZmNw$JKin7ahjmh8JYvMyFcXri0Ss/Uvd3uYpD7MG6C/5Cy0'));
var_dump(password_verify('argon2', '$argon2i$v=19$m=65536,t=3,p=1$OEVjWWs2Z3YvWlNZQ0ZmNw$JKin7ahjmh8JYvMyFcXri0Ss/Uvd3uYpD7MG6C/5Cy0'));
var_dump(password_verify('test', '$argon2id$v=19$m=1024,t=2,p=2$WS90MHJhd3AwSC5xTDJpZg$8tn2DaIJR2/UX4Cjcy2t3EZaLDL/qh+NbLQAOvTmdAg'));
var_dump(password_verify('argon2id', '$argon2id$v=19$m=1024,t=2,p=2$WS90MHJhd3AwSC5xTDJpZg$8tn2DaIJR2/UX4Cjcy2t3EZaLDL/qh+NbLQAOvTmdAg'));
echo "OK!";
?>
--EXPECT--
bool(true)
bool(false)
bool(true)
bool(false)
OK!

0 comments on commit 55277a6

Please sign in to comment.
You can’t perform that action at this time.