Skip to content

Create hash_pbkdf2 function addition #105

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

Merged
merged 8 commits into from
Jul 10, 2012
Merged
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
5 changes: 3 additions & 2 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ PHP NEWS
(Nikita Popov)

- Core:
. Fixed bug #62443 (Crypt SHA256/512 Segfaults With Malformed
Salt). (Anthony Ferrara)
. Added boolval(). (Jille Timmermans).
. Fixed bug #61681 (Malformed grammar). (Nikita Popov, Etienne, Laruence).
. Fixed bug #61038 (unpack("a5", "str\0\0") does not work as expected).
Expand Down Expand Up @@ -44,6 +42,9 @@ PHP NEWS
still exists for backward compatibility but is doing nothing). (Pierrick)
. Fixed bug #54995 (Missing CURLINFO_RESPONSE_CODE support). (Pierrick)

- Hash
. Added support for PBKDF2 via hash_pbkdf2(). (Anthony Ferrara)

- MySQLi
. Dropped support for LOAD DATA LOCAL INFILE handlers when using libmysql.
Known for stability problems. (Andrey)
Expand Down
210 changes: 178 additions & 32 deletions ext/hash/hash.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "config.h"
#endif

#include <math.h>
#include "php_hash.h"
#include "ext/standard/info.h"
#include "ext/standard/file.h"
Expand Down Expand Up @@ -202,10 +203,45 @@ PHP_FUNCTION(hash_file)
}
/* }}} */

static inline void php_hash_string_xor_char(unsigned char *out, const unsigned char *in, const unsigned char xor_with, const int length) {
int i;
for (i=0; i < length; i++) {
out[i] = in[i] ^ xor_with;
}
}

static inline void php_hash_string_xor(unsigned char *out, const unsigned char *in, const unsigned char *xor_with, const int length) {
int i;
for (i=0; i < length; i++) {
out[i] = in[i] ^ xor_with[i];
}
}

static inline void php_hash_hmac_prep_key(unsigned char *K, const php_hash_ops *ops, void *context, const unsigned char *key, const int key_len) {
memset(K, 0, ops->block_size);
if (key_len > ops->block_size) {
/* Reduce the key first */
ops->hash_init(context);
ops->hash_update(context, key, key_len);
ops->hash_final(K, context);
} else {
memcpy(K, key, key_len);
}
/* XOR the key with 0x36 to get the ipad) */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a leftover ) at the end of the comment :)

php_hash_string_xor_char(K, K, 0x36, ops->block_size);
}

static inline void php_hash_hmac_round(unsigned char *final, const php_hash_ops *ops, void *context, const unsigned char *key, const unsigned char *data, const long data_size) {
ops->hash_init(context);
ops->hash_update(context, key, ops->block_size);
ops->hash_update(context, data, data_size);
ops->hash_final(final, context);
}

static void php_hash_do_hash_hmac(INTERNAL_FUNCTION_PARAMETERS, int isfilename, zend_bool raw_output_default) /* {{{ */
{
char *algo, *data, *digest, *key, *K;
int algo_len, data_len, key_len, i;
int algo_len, data_len, key_len;
zend_bool raw_output = raw_output_default;
const php_hash_ops *ops;
void *context;
Expand All @@ -230,52 +266,29 @@ static void php_hash_do_hash_hmac(INTERNAL_FUNCTION_PARAMETERS, int isfilename,
}

context = emalloc(ops->context_size);
ops->hash_init(context);

K = emalloc(ops->block_size);
memset(K, 0, ops->block_size);
digest = emalloc(ops->digest_size + 1);

if (key_len > ops->block_size) {
/* Reduce the key first */
ops->hash_update(context, (unsigned char *) key, key_len);
ops->hash_final((unsigned char *) K, context);
/* Make the context ready to start over */
ops->hash_init(context);
} else {
memcpy(K, key, key_len);
}

/* XOR ipad */
for(i=0; i < ops->block_size; i++) {
K[i] ^= 0x36;
}
ops->hash_update(context, (unsigned char *) K, ops->block_size);
php_hash_hmac_prep_key((unsigned char *) K, ops, context, (unsigned char *) key, key_len);

if (isfilename) {
char buf[1024];
int n;

ops->hash_init(context);
ops->hash_update(context, (unsigned char *) K, ops->block_size);
while ((n = php_stream_read(stream, buf, sizeof(buf))) > 0) {
ops->hash_update(context, (unsigned char *) buf, n);
}
php_stream_close(stream);
ops->hash_final((unsigned char *) digest, context);
} else {
ops->hash_update(context, (unsigned char *) data, data_len);
php_hash_hmac_round((unsigned char *) digest, ops, context, (unsigned char *) K, (unsigned char *) data, data_len);
}

digest = emalloc(ops->digest_size + 1);
ops->hash_final((unsigned char *) digest, context);

/* Convert K to opad -- 0x6A = 0x36 ^ 0x5C */
for(i=0; i < ops->block_size; i++) {
K[i] ^= 0x6A;
}
php_hash_string_xor_char((unsigned char *) K, (unsigned char *) K, 0x6A, ops->block_size);

/* Feed this result into the outter hash */
ops->hash_init(context);
ops->hash_update(context, (unsigned char *) K, ops->block_size);
ops->hash_update(context, (unsigned char *) digest, ops->digest_size);
ops->hash_final((unsigned char *) digest, context);
php_hash_hmac_round((unsigned char *) digest, ops, context, (unsigned char *) K, (unsigned char *) digest, ops->digest_size);

/* Zero the key */
memset(K, 0, ops->block_size);
Expand Down Expand Up @@ -591,6 +604,128 @@ PHP_FUNCTION(hash_algos)
}
/* }}} */

/* {{{ 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 */
PHP_FUNCTION(hash_pbkdf2)
{
char *returnval, *algo, *salt, *pass = NULL;
unsigned char *computed_salt, *digest, *temp, *result, *K1, *K2 = NULL;
long loops, i, j, algo_len, pass_len, iterations, length, digest_length = 0;
int argc, salt_len = 0;
zend_bool raw_output = 0;
const php_hash_ops *ops;
void *context;

argc = ZEND_NUM_ARGS();
if (zend_parse_parameters(argc TSRMLS_CC, "sssl|lb", &algo, &algo_len, &pass, &pass_len, &salt, &salt_len, &iterations, &length, &raw_output) == FAILURE) {
return;
}

ops = php_hash_fetch_ops(algo, algo_len);
if (!ops) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unknown hashing algorithm: %s", algo);
RETURN_FALSE;
}

if (iterations <= 0) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Iterations must be a positive integer: %ld", iterations);
RETURN_FALSE;
}

if (length < 0) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Length must be greater than or equal to 0: %ld", length);
RETURN_FALSE;
}

if (salt_len > INT_MAX - 4) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Supplied salt is too long, max of INT_MAX - 4 bytes: %d supplied", salt_len);
RETURN_FALSE;
}

context = emalloc(ops->context_size);
ops->hash_init(context);

K1 = emalloc(ops->block_size);
K2 = emalloc(ops->block_size);
digest = emalloc(ops->digest_size);
temp = emalloc(ops->digest_size);

/* Setup Keys that will be used for all hmac rounds */
php_hash_hmac_prep_key(K1, ops, context, (unsigned char *) pass, pass_len);
/* Convert K1 to opad -- 0x6A = 0x36 ^ 0x5C */
php_hash_string_xor_char(K2, K1, 0x6A, ops->block_size);

/* Setup Main Loop to build a long enough result */
if (length == 0) {
length = ops->digest_size;
}
digest_length = length;
if (!raw_output) {
digest_length = (long) ceil((float) length / 2.0);
}

loops = (long) ceil((float) digest_length / (float) ops->digest_size);

result = safe_emalloc(loops, ops->digest_size, 0);

computed_salt = safe_emalloc(salt_len, 1, 4);
memcpy(computed_salt, (unsigned char *) salt, salt_len);

for (i = 1; i <= loops; i++) {
/* digest = hash_hmac(salt + pack('N', i), password) { */

/* pack("N", i) */
computed_salt[salt_len] = (unsigned char) (i >> 24);
computed_salt[salt_len + 1] = (unsigned char) ((i & 0xFF0000) >> 16);
computed_salt[salt_len + 2] = (unsigned char) ((i & 0xFF00) >> 8);
computed_salt[salt_len + 3] = (unsigned char) (i & 0xFF);

php_hash_hmac_round(digest, ops, context, K1, computed_salt, (long) salt_len + 4);
php_hash_hmac_round(digest, ops, context, K2, digest, ops->digest_size);
/* } */

/* temp = digest */
memcpy(temp, digest, ops->digest_size);

/*
* Note that the loop starting at 1 is intentional, since we've already done
* the first round of the algorithm.
*/
for (j = 1; j < iterations; j++) {
/* digest = hash_hmac(digest, password) { */
php_hash_hmac_round(digest, ops, context, K1, digest, ops->digest_size);
php_hash_hmac_round(digest, ops, context, K2, digest, ops->digest_size);
/* } */
/* temp ^= digest */
php_hash_string_xor(temp, temp, digest, ops->digest_size);
}
/* result += temp */
memcpy(result + ((i - 1) * ops->digest_size), temp, ops->digest_size);
}
/* Zero potentially sensitive variables */
memset(K1, 0, ops->block_size);
memset(K2, 0, ops->block_size);
memset(computed_salt, 0, salt_len + 4);
efree(K1);
efree(K2);
efree(computed_salt);
efree(context);
efree(digest);
efree(temp);

returnval = safe_emalloc(length, 1, 1);
if (raw_output) {
memcpy(returnval, result, length);
} else {
php_hash_bin2hex(returnval, result, digest_length);
}
returnval[length] = 0;
efree(result);
RETURN_STRINGL(returnval, length, 0);
}
/* }}} */

/* Module Housekeeping */

static void php_hash_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC) /* {{{ */
Expand Down Expand Up @@ -1003,6 +1138,15 @@ ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(arginfo_hash_algos, 0)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_hash_pbkdf2, 0, 0, 4)
ZEND_ARG_INFO(0, algo)
ZEND_ARG_INFO(0, password)
ZEND_ARG_INFO(0, salt)
ZEND_ARG_INFO(0, iterations)
ZEND_ARG_INFO(0, length)
ZEND_ARG_INFO(0, raw_output)
ZEND_END_ARG_INFO()

/* BC Land */
#ifdef PHP_MHASH_BC
ZEND_BEGIN_ARG_INFO(arginfo_mhash_get_block_size, 0)
Expand Down Expand Up @@ -1049,6 +1193,7 @@ const zend_function_entry hash_functions[] = {
PHP_FE(hash_copy, arginfo_hash_copy)

PHP_FE(hash_algos, arginfo_hash_algos)
PHP_FE(hash_pbkdf2, arginfo_hash_pbkdf2)

/* BC Land */
#ifdef PHP_HASH_MD5_NOT_IN_CORE
Expand Down Expand Up @@ -1105,3 +1250,4 @@ ZEND_GET_MODULE(hash)
* vim600: noet sw=4 ts=4 fdm=marker
* vim<600: noet sw=4 ts=4
*/

1 change: 1 addition & 0 deletions ext/hash/php_hash.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ PHP_FUNCTION(hash_update_stream);
PHP_FUNCTION(hash_update_file);
PHP_FUNCTION(hash_final);
PHP_FUNCTION(hash_algos);
PHP_FUNCTION(hash_pbkdf2);

PHP_HASH_API const php_hash_ops *php_hash_fetch_ops(const char *algo, int algo_len);
PHP_HASH_API void php_hash_register_algo(const char *algo, const php_hash_ops *ops);
Expand Down
37 changes: 37 additions & 0 deletions ext/hash/tests/hash_pbkdf2_basic.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
--TEST--
Test hash_pbkdf2() function : basic functionality
--SKIPIF--
<?php extension_loaded('hash') or die('skip: hash extension not loaded.'); ?>
--FILE--
<?php

/* Prototype : string hash_hmac ( string $algo , string $data , string $key [, bool $raw_output ] )
* Description: Generate a keyed hash value using the HMAC method
* Source code: ext/hash/hash.c
* Alias to functions:
*/

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

echo "sha1: " . hash_pbkdf2('sha1', 'password', 'salt', 1, 20)."\n";
echo "sha1(raw): " . bin2hex(hash_pbkdf2('sha1', 'password', 'salt', 1, 20, TRUE))."\n";
echo "sha1(rounds): " . hash_pbkdf2('sha1', 'passwordPASSWORDpassword', 'saltSALTsaltSALTsaltSALTsaltSALTsalt', 4096, 25)."\n";
echo "sha1(rounds)(raw): " . bin2hex(hash_pbkdf2('sha1', 'passwordPASSWORDpassword', 'saltSALTsaltSALTsaltSALTsaltSALTsalt', 4096, 25, TRUE))."\n";
echo "sha256: " . hash_pbkdf2('sha256', 'password', 'salt', 1, 20)."\n";
echo "sha256(raw): " . bin2hex(hash_pbkdf2('sha256', 'password', 'salt', 1, 20, TRUE))."\n";
echo "sha256(rounds): " . hash_pbkdf2('sha256', 'passwordPASSWORDpassword', 'saltSALTsaltSALTsaltSALTsaltSALTsalt', 4096, 40)."\n";
echo "sha256(rounds)(raw): " . bin2hex(hash_pbkdf2('sha256', 'passwordPASSWORDpassword', 'saltSALTsaltSALTsaltSALTsaltSALTsalt', 4096, 40, TRUE))."\n";

?>
===Done===
--EXPECT--
*** Testing hash_pbkdf2() : basic functionality ***
sha1: 0c60c80f961f0e71f3a9
sha1(raw): 0c60c80f961f0e71f3a9b524af6012062fe037a6
sha1(rounds): 3d2eec4fe41c849b80c8d8366
sha1(rounds)(raw): 3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038
sha256: 120fb6cffcf8b32c43e7
sha256(raw): 120fb6cffcf8b32c43e7225256c4f837a86548c9
sha256(rounds): 348c89dbcbd32b2f32d814b8116e84cf2b17347e
sha256(rounds)(raw): 348c89dbcbd32b2f32d814b8116e84cf2b17347ebc1800181c4e2a1fb8dd53e1c635518c7dac47e9
===Done===
Loading