--- ext/hash/php_hash.h.pbkdf2	2013-01-16 09:10:30.000000000 +0200
+++ ext/hash/php_hash.h	2013-01-22 17:10:21.728954248 +0200
@@ -127,6 +127,7 @@
 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);
--- ext/hash/hash.c.pbkdf2	2013-01-16 09:10:30.000000000 +0200
+++ ext/hash/hash.c	2013-01-22 17:34:13.402953353 +0200
@@ -202,6 +202,41 @@
 }
 /* }}} */
 
+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) */
+	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;
@@ -591,6 +626,128 @@
 }
 /* }}} */
 
+/* {{{ 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) /* {{{ */
@@ -1003,6 +1160,15 @@
 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)
@@ -1049,6 +1215,7 @@
 	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
