Skip to content

Add hash_hkdf() to ext/hash #1105

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions ext/hash/hash.c
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,128 @@ PHP_FUNCTION(hash_algos)
}
/* }}} */

static inline int php_hash_is_crypto(const char *algo, size_t algo_len) {

char *blacklist[] = { "adler32", "crc32", "crc32b", "fnv132", "fnv1a32", "fnv164", "fnv1a64", "joaat", 0 };
char *lower = zend_str_tolower_dup(algo, algo_len);
int i = 0;

while (blacklist[i]) {
if (strcmp(lower, blacklist[i]) == 0) {
i = -1;
break;
}

i++;
}

efree(lower);
return i;
}

/* {{{ proto string hash_hkdf(string algo, string ikm [, int length = 0, string info = '', string salt = ''])
RFC5869 HMAC-based key derivation function */
PHP_FUNCTION(hash_hkdf)
{
zend_string *returnval, *ikm, *algo, *info = NULL, *salt = NULL;
zend_long length = 0;
char *prk;
unsigned char *digest, *K;
int i, rounds;
const php_hash_ops *ops;
void *context;

if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS|lSS", &algo, &ikm, &length, &info, &salt) == FAILURE) {
return;
}

ops = php_hash_fetch_ops(ZSTR_VAL(algo), ZSTR_LEN(algo));
if (!ops) {
php_error_docref(NULL, E_WARNING, "Unknown hashing algorithm: %s", ZSTR_VAL(algo));
RETURN_FALSE;
}
else if (php_hash_is_crypto(ZSTR_VAL(algo), ZSTR_LEN(algo)) == -1) {
php_error_docref(NULL, E_WARNING, "Non-cryptographic hashing algorithm: %s", ZSTR_VAL(algo));
RETURN_FALSE;
}

if (ZSTR_LEN(ikm) == 0) {
php_error_docref(NULL, E_WARNING, "Input keying material cannot be empty");
RETURN_FALSE;
}

if (length < 0) {
php_error_docref(NULL, E_WARNING, "Length must be greater than or equal to 0: " ZEND_LONG_FMT, length);
RETURN_FALSE;
}
else if (length == 0) {
length = ops->digest_size;
}
else if (length > ops->digest_size * 255) {
php_error_docref(NULL, E_WARNING, "Length must be less than or equal to %d: " ZEND_LONG_FMT, ops->digest_size * 255, length);
RETURN_FALSE;
}

context = emalloc(ops->context_size);

// Extract
ops->hash_init(context);
K = emalloc(ops->block_size);
if (salt != NULL && ZSTR_LEN(salt) > 0) {
php_hash_hmac_prep_key(K, ops, context, ZSTR_VAL(salt), ZSTR_LEN(salt));
}
else {
memset(K, 0x36, ops->block_size);
}

prk = emalloc(ops->digest_size);
php_hash_hmac_round(prk, ops, context, K, ZSTR_VAL(ikm), ZSTR_LEN(ikm));
php_hash_string_xor_char(K, K, 0x6A, ops->block_size);
php_hash_hmac_round(prk, ops, context, K, prk, ops->digest_size);
ZEND_SECURE_ZERO(K, ops->block_size);

// Expand
returnval = zend_string_alloc(length, 0);
digest = emalloc(ops->digest_size);
for (i = 1, rounds = (length - 1) / ops->digest_size + 1; i <= rounds; i++) {
// chr(i)
unsigned char c[1];
c[0] = (i & 0xFF);

php_hash_hmac_prep_key(K, ops, context, prk, ops->digest_size);
ops->hash_init(context);
ops->hash_update(context, K, ops->block_size);

if (i > 1) {
ops->hash_update(context, digest, ops->digest_size);
}

if (info != NULL && ZSTR_LEN(info) > 0) {
ops->hash_update(context, ZSTR_VAL(info), ZSTR_LEN(info));
}

ops->hash_update(context, c, 1);
ops->hash_final(digest, context);
php_hash_string_xor_char(K, K, 0x6A, ops->block_size);
php_hash_hmac_round(digest, ops, context, K, digest, ops->digest_size);
memcpy(
ZSTR_VAL(returnval) + ((i - 1) * ops->digest_size),
digest,
(i == rounds ? length - ((i - 1) * ops->digest_size) : ops->digest_size)
);
}

ZEND_SECURE_ZERO(K, ops->block_size);
ZEND_SECURE_ZERO(digest, ops->digest_size);
ZEND_SECURE_ZERO(prk, ops->digest_size);
efree(K);
efree(context);
efree(prk);
efree(digest);
ZSTR_VAL(returnval)[length] = 0;
RETURN_STR(returnval);
}

/* {{{ proto string hash_pbkdf2(string algo, string password, string salt, int iterations [, int length = 0, bool raw_output = false])
Generate a PBKDF2 hash of the given password and salt
Returns lowercase hexits by default */
Expand Down Expand Up @@ -1205,6 +1327,14 @@ ZEND_BEGIN_ARG_INFO(arginfo_hash_equals, 0)
ZEND_ARG_INFO(0, user_string)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_hash_hkdf, 0, 0, 2)
ZEND_ARG_INFO(0, ikm)
ZEND_ARG_INFO(0, algo)
ZEND_ARG_INFO(0, length)
ZEND_ARG_INFO(0, string)
ZEND_ARG_INFO(0, salt)
ZEND_END_ARG_INFO()

/* BC Land */
#ifdef PHP_MHASH_BC
ZEND_BEGIN_ARG_INFO(arginfo_mhash_get_block_size, 0)
Expand Down Expand Up @@ -1253,6 +1383,7 @@ const zend_function_entry hash_functions[] = {
PHP_FE(hash_algos, arginfo_hash_algos)
PHP_FE(hash_pbkdf2, arginfo_hash_pbkdf2)
PHP_FE(hash_equals, arginfo_hash_equals)
PHP_FE(hash_hkdf, arginfo_hash_hkdf)

/* BC Land */
#ifdef PHP_HASH_MD5_NOT_IN_CORE
Expand Down
1 change: 1 addition & 0 deletions ext/hash/php_hash.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ extern zend_module_entry hash_module_entry;

PHP_FUNCTION(hash);
PHP_FUNCTION(hash_file);
PHP_FUNCTION(hash_hkdf);
PHP_FUNCTION(hash_hmac);
PHP_FUNCTION(hash_hmac_file);
PHP_FUNCTION(hash_init);
Expand Down
94 changes: 94 additions & 0 deletions ext/hash/tests/hash_hkdf_basic.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
--TEST--
Test hash_hkdf() function: basic functionality
--SKIPIF--
<?php extension_loaded('hash') or die('skip: hash extension not loaded.'); ?>
--FILE--
<?php

/* Prototype : string hkdf ( string $algo , string $ikm [, int $length , string $info = '' , string $salt = '' ] )
* Description: HMAC-based Key Derivation Function
* Source code: ext/hash/hash.c
*/

echo "*** Testing hash_hkdf(): basic functionality ***\n";

$ikm = 'input key material';

echo 'md2: ', bin2hex(hash_hkdf('md2', $ikm)), "\n";
echo 'md4: ', bin2hex(hash_hkdf('md4', $ikm)), "\n";
echo 'md5: ', bin2hex(hash_hkdf('md5', $ikm)), "\n";
echo 'sha1: ', bin2hex(hash_hkdf('sha1', $ikm)), "\n";
echo 'sha224: ', bin2hex(hash_hkdf('sha224', $ikm)), "\n";
echo 'sha256: ', bin2hex(hash_hkdf('sha256', $ikm)), "\n";
echo 'sha384: ', bin2hex(hash_hkdf('sha384', $ikm)), "\n";
echo 'sha512: ', bin2hex(hash_hkdf('sha512', $ikm)), "\n";
echo 'ripemd128: ', bin2hex(hash_hkdf('ripemd128', $ikm)), "\n";
echo 'ripemd160: ', bin2hex(hash_hkdf('ripemd160', $ikm)), "\n";
echo 'ripemd256: ', bin2hex(hash_hkdf('ripemd256', $ikm)), "\n";
echo 'ripemd320: ', bin2hex(hash_hkdf('ripemd320', $ikm)), "\n";
echo 'whirlpool: ', bin2hex(hash_hkdf('whirlpool', $ikm)), "\n";
echo 'tiger128,3: ', bin2hex(hash_hkdf('tiger128,3', $ikm)), "\n";
echo 'tiger160,3: ', bin2hex(hash_hkdf('tiger160,3', $ikm)), "\n";
echo 'tiger192,3: ', bin2hex(hash_hkdf('tiger192,3', $ikm)), "\n";
echo 'tiger128,4: ', bin2hex(hash_hkdf('tiger128,4', $ikm)), "\n";
echo 'tiger160,4: ', bin2hex(hash_hkdf('tiger160,4', $ikm)), "\n";
echo 'tiger192,4: ', bin2hex(hash_hkdf('tiger192,4', $ikm)), "\n";
echo 'haval128,3: ', bin2hex(hash_hkdf('haval128,3', $ikm)), "\n";
echo 'haval160,3: ', bin2hex(hash_hkdf('haval160,3', $ikm)), "\n";
echo 'haval192,3: ', bin2hex(hash_hkdf('haval192,3', $ikm)), "\n";
echo 'haval224,3: ', bin2hex(hash_hkdf('haval224,3', $ikm)), "\n";
echo 'haval256,3: ', bin2hex(hash_hkdf('haval256,3', $ikm)), "\n";
echo 'haval128,4: ', bin2hex(hash_hkdf('haval128,4', $ikm)), "\n";
echo 'haval160,4: ', bin2hex(hash_hkdf('haval160,4', $ikm)), "\n";
echo 'haval192,4: ', bin2hex(hash_hkdf('haval192,4', $ikm)), "\n";
echo 'haval224,4: ', bin2hex(hash_hkdf('haval224,4', $ikm)), "\n";
echo 'haval256,4: ', bin2hex(hash_hkdf('haval256,4', $ikm)), "\n";
echo 'haval128,5: ', bin2hex(hash_hkdf('haval128,5', $ikm)), "\n";
echo 'haval160,5: ', bin2hex(hash_hkdf('haval160,5', $ikm)), "\n";
echo 'haval192,5: ', bin2hex(hash_hkdf('haval192,5', $ikm)), "\n";
echo 'haval224,5: ', bin2hex(hash_hkdf('haval224,5', $ikm)), "\n";
echo 'haval256,5: ', bin2hex(hash_hkdf('haval256,5', $ikm)), "\n";
echo 'snefru: ', bin2hex(hash_hkdf('snefru', $ikm)), "\n";
echo 'snefru256: ', bin2hex(hash_hkdf('snefru256', $ikm)), "\n";
echo 'gost: ', bin2hex(hash_hkdf('gost', $ikm)), "\n";

?>
--EXPECTF--
*** Testing hash_hkdf(): basic functionality ***
md2: 87779851d2377dab25da16fd7aadfdf5
md4: 422c6bd8dd2a6baae8abadef618c3ede
md5: 98b16391063ecee006a3ca8ee5776b1e
sha1: a71863230e3782240265126a53e137af6667e988
sha224: 51678ceb17e803505187b2cf6451c30fbc572fda165bb69bbd117c7a
sha256: d8f0bede4b652933c32a92eccf7723f7eeb4701744c81325dc3f0fa9fda24499
sha384: f600680e677bb417a7a22a4da8b167c0d91823a7a5d56a49aeb1838bb2320c05068d15d6d980824fee542a279d310c3a
sha512: fb1b86549e941b81821a89ac6ba7c4f93465077b3f2af94352ebf1d041efcd3c5694469c1ae31bb10db4c1d2ab84f07e4518ba33a3eadd4a149425750285c640
ripemd128: cb6418fc0dc9efaeb7e9654390fa7f14
ripemd160: ba42dbb34f08e9337ace15295f218754a41d6c39
ripemd256: f2e96b292935e2395b59833ed89d928ac1197ff62c8031ebc06a3f5bad19513f
ripemd320: a13a682072525ceb4c4a5fef59096e682096e1096e6e7e238c7bd48a6f6c6a9ba3d7d9fbee6b68c4
whirlpool: 497c717e04d896c3d582742c614435b7d0963b39de12dcf532540d39164b3b85214014620dfdff4a089a06b06aff43c39a3b4d9b806913cf6309de58ff1151f5
tiger128,3: e13c2e7262892c6bd8dfc24121e7cb34
tiger160,3: 48cc5a9f5e5d7029eb0544662222c0ba13822b7b
tiger192,3: 5a665d23b6cbb405668160e58b01aebef74eba979f4bc70b
tiger128,4: 8acf517ecf58cccbd65c1186d71e4116
tiger160,4: cc0e33ee26700a2eb9a994bbb0e6cef29b429441
tiger192,4: 97fa02d42331321fdc05c7f8dbc756d751ca36ce1aee69b0
haval128,3: 2accab8029d42fb15fdbe9d3e2a470ca
haval160,3: 496fd29e7fc8351d2971b96a3733a7b3de000064
haval192,3: 238a731801439b1f195e1a1568ce75251e1dd719d904a8a2
haval224,3: d863e596ff6b2bdba1ed7b313df1c3d177176312e81b47e9290f7566
haval256,3: 96f555fe41255c34fe57b275f1ae40bbb8f07c6a2a6d68c849748fbb393ff443
haval128,4: 9822af229cc59527a72e231a690fad3b
haval160,4: 1bbbc4d632daaf94d5ba167efaa70af5b753effe
haval192,4: dd12a8f8919cbf5632497f0918b30236371dd1b55f71e824
haval224,4: 8af449fb4eb627eb8887507c1279a116ac4325b5806dd22e2f2af410
haval256,4: bd74a6d5fa1ec23a92ce1fd76c36bc8be36f5eddbea821545a91810e1f8d6fc5
haval128,5: 84564f3450a6ccf6041162207dc8acba
haval160,5: b55cd1b3c514457b9e61c51ad22f302f6ec7cca1
haval192,5: d1db7a8e69b327455d530d1ac60f774023b8b4bdd6bbbf92
haval224,5: c5a2576511f1143c6e29f63d82d6e0be8f67d0bea448e27238be5000
haval256,5: 9dbab73d13f1fd3a1b41398fe90ba1f298329681d861b023373c33f1051bd4d3
snefru: 798eac954e5ece38e9acb63b50c1c2ecb799d34356358cec5a80eeeea91c8de9
snefru256: 798eac954e5ece38e9acb63b50c1c2ecb799d34356358cec5a80eeeea91c8de9
gost: 64edd584b87a2dfdd1f2b44ed2db8bd27af8386aafe751c2aebaed32dfa3852e
32 changes: 32 additions & 0 deletions ext/hash/tests/hash_hkdf_edges.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
--TEST--
Test hash_hkdf() function: edge cases
--SKIPIF--
<?php extension_loaded('hash') or die('skip: hash extension not loaded.'); ?>
--FILE--
<?php

/* Prototype : string hkdf ( string $algo , string $ikm [, int $length , string $info = '' , string $salt = '' ] )
* Description: HMAC-based Key Derivation Function
* Source code: ext/hash/hash.c
*/

echo "*** Testing hash_hkdf(): edge cases ***\n";

$ikm = 'input key material';

echo 'Length < digestSize: ', bin2hex(hash_hkdf('md5', $ikm, 7)), "\n";
echo 'Length % digestSize != 0: ', bin2hex(hash_hkdf('md5', $ikm, 17)), "\n";
echo 'Algo name case-sensitivity: ', (bin2hex(hash_hkdf('Md5', $ikm, 7)) === '98b16391063ece' ? 'true' : 'false'), "\n";
echo "Non-crypto algo name case-sensitivity:\n";
var_dump(hash_hkdf('jOaAt', $ikm));

?>
--EXPECTF--
*** Testing hash_hkdf(): edge cases ***
Length < digestSize: 98b16391063ece
Length % digestSize != 0: 98b16391063ecee006a3ca8ee5776b1e5f
Algo name case-sensitivity: true
Non-crypto algo name case-sensitivity:

Warning: hash_hkdf(): Non-cryptographic hashing algorithm: jOaAt in %s on line %d
bool(false)
100 changes: 100 additions & 0 deletions ext/hash/tests/hash_hkdf_error.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
--TEST--
Test hash_hkdf() function: error conditions
--SKIPIF--
<?php extension_loaded('hash') or die('skip: hash extension not loaded.'); ?>
--FILE--
<?php

/* Prototype : string hkdf ( string $algo , string $ikm [, int $length , string $info = '' , string $salt = '' ] )
* Description: HMAC-based Key Derivation Function
* Source code: ext/hash/hash.c
*/

$ikm = 'input key material';

echo "*** Testing hash_hkdf(): error conditions ***\n";

echo "\n-- Testing hash_hkdf() function with less than expected no. of arguments --\n";
var_dump(hash_hkdf());
var_dump(hash_hkdf('sha1'));

echo "\n-- Testing hash_hkdf() function with more than expected no. of arguments --\n";
var_dump(hash_hkdf('sha1', $ikm, 20, '', '', 'extra parameter'));

echo "\n-- Testing hash_hkdf() function with invalid hash algorithm --\n";
var_dump(hash_hkdf('foo', $ikm));

echo "\n-- Testing hash_hkdf() function with non-cryptographic hash algorithm --\n";
var_dump(hash_hkdf('adler32', $ikm));
var_dump(hash_hkdf('crc32', $ikm));
var_dump(hash_hkdf('crc32b', $ikm));
var_dump(hash_hkdf('fnv132', $ikm));
var_dump(hash_hkdf('fnv1a32', $ikm));
var_dump(hash_hkdf('fnv164', $ikm));
var_dump(hash_hkdf('fnv1a64', $ikm));
var_dump(hash_hkdf('joaat', $ikm));

echo "\n-- Testing hash_hkdf() function with invalid parameters --\n";
var_dump(hash_hkdf('sha1', ''));
var_dump(hash_hkdf('sha1', $ikm, -1));
var_dump(hash_hkdf('sha1', $ikm, 20 * 255 + 1)); // Length can't be more than 255 times the hash digest size
?>
===Done===
--EXPECTF--
*** Testing hash_hkdf(): error conditions ***

-- Testing hash_hkdf() function with less than expected no. of arguments --

Warning: hash_hkdf() expects at least 2 parameters, 0 given in %s on line %d
NULL

Warning: hash_hkdf() expects at least 2 parameters, 1 given in %s on line %d
NULL

-- Testing hash_hkdf() function with more than expected no. of arguments --

Warning: hash_hkdf() expects at most 5 parameters, 6 given in %s on line %d
NULL

-- Testing hash_hkdf() function with invalid hash algorithm --

Warning: hash_hkdf(): Unknown hashing algorithm: foo in %s on line %d
bool(false)

-- Testing hash_hkdf() function with non-cryptographic hash algorithm --

Warning: hash_hkdf(): Non-cryptographic hashing algorithm: adler32 in %s on line %d
bool(false)

Warning: hash_hkdf(): Non-cryptographic hashing algorithm: crc32 in %s on line %d
bool(false)

Warning: hash_hkdf(): Non-cryptographic hashing algorithm: crc32b in %s on line %d
bool(false)

Warning: hash_hkdf(): Non-cryptographic hashing algorithm: fnv132 in %s on line %d
bool(false)

Warning: hash_hkdf(): Non-cryptographic hashing algorithm: fnv1a32 in %s on line %d
bool(false)

Warning: hash_hkdf(): Non-cryptographic hashing algorithm: fnv164 in %s on line %d
bool(false)

Warning: hash_hkdf(): Non-cryptographic hashing algorithm: fnv1a64 in %s on line %d
bool(false)

Warning: hash_hkdf(): Non-cryptographic hashing algorithm: joaat in %s on line %d
bool(false)

-- Testing hash_hkdf() function with invalid parameters --

Warning: hash_hkdf(): Input keying material cannot be empty in %s on line %d
bool(false)

Warning: hash_hkdf(): Length must be greater than or equal to 0: -1 in %s on line %d
bool(false)

Warning: hash_hkdf(): Length must be less than or equal to 5100: 5101 in %s on line %d
bool(false)
===Done===
Loading