diff --git a/apps/encryption/controller/settingscontroller.php b/apps/encryption/controller/settingscontroller.php
index 641c97e1d6e37b2808f3d3932bbab550301398a3..dbd960bb7843ed0593a66ceb62573e51cd89348f 100644
--- a/apps/encryption/controller/settingscontroller.php
+++ b/apps/encryption/controller/settingscontroller.php
@@ -103,7 +103,7 @@ class SettingsController extends Controller {
 			$decryptedKey = $this->crypt->decryptPrivateKey($encryptedKey, $oldPassword);
 
 			if ($decryptedKey) {
-				$encryptedKey = $this->crypt->symmetricEncryptFileContent($decryptedKey, $newPassword);
+				$encryptedKey = $this->crypt->encryptPrivateKey($decryptedKey, $newPassword);
 				$header = $this->crypt->generateHeader();
 				if ($encryptedKey) {
 					$this->keyManager->setPrivateKey($uid, $header . $encryptedKey);
diff --git a/apps/encryption/hooks/userhooks.php b/apps/encryption/hooks/userhooks.php
index a86b8662781dd7e924ce0868c91965fd5832d8e6..71c0435d200cd5d4bf2a8606a3cad53a30ccf88e 100644
--- a/apps/encryption/hooks/userhooks.php
+++ b/apps/encryption/hooks/userhooks.php
@@ -220,8 +220,7 @@ class UserHooks implements IHook {
 		if ($user && $params['uid'] === $user->getUID() && $privateKey) {
 
 			// Encrypt private key with new user pwd as passphrase
-			$encryptedPrivateKey = $this->crypt->symmetricEncryptFileContent($privateKey,
-				$params['password']);
+			$encryptedPrivateKey = $this->crypt->encryptPrivateKey($privateKey, $params['password']);
 
 			// Save private key
 			if ($encryptedPrivateKey) {
@@ -259,8 +258,7 @@ class UserHooks implements IHook {
 				$this->keyManager->setPublicKey($user, $keyPair['publicKey']);
 
 				// Encrypt private key with new password
-				$encryptedKey = $this->crypt->symmetricEncryptFileContent($keyPair['privateKey'],
-					$newUserPassword);
+				$encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], $newUserPassword);
 
 				if ($encryptedKey) {
 					$this->keyManager->setPrivateKey($user, $this->crypt->generateHeader() . $encryptedKey);
diff --git a/apps/encryption/lib/crypto/crypt.php b/apps/encryption/lib/crypto/crypt.php
index f3cf38fb96c8c55ddfbdbc2b783be0843da10519..eef16e51447bd6978f6c61cd61b08eb559075200 100644
--- a/apps/encryption/lib/crypto/crypt.php
+++ b/apps/encryption/lib/crypto/crypt.php
@@ -30,6 +30,7 @@ use OC\Encryption\Exceptions\DecryptionFailedException;
 use OC\Encryption\Exceptions\EncryptionFailedException;
 use OCA\Encryption\Exceptions\MultiKeyDecryptException;
 use OCA\Encryption\Exceptions\MultiKeyEncryptException;
+use OCA\Encryption\Vendor\PBKDF2Fallback;
 use OCP\Encryption\Exceptions\GenericEncryptionException;
 use OCP\IConfig;
 use OCP\ILogger;
@@ -42,6 +43,10 @@ class Crypt {
 	// default cipher from old ownCloud versions
 	const LEGACY_CIPHER = 'AES-128-CFB';
 
+	// default key format, old ownCloud version encrypted the private key directly
+	// with the user password
+	const LEGACY_KEY_FORMAT = 'password';
+
 	const HEADER_START = 'HBEGIN';
 	const HEADER_END = 'HEND';
 	/**
@@ -57,6 +62,11 @@ class Crypt {
 	 */
 	private $config;
 
+	/**
+	 * @var array
+	 */
+	private $supportedKeyFormats;
+
 	/**
 	 * @param ILogger $logger
 	 * @param IUserSession $userSession
@@ -66,6 +76,7 @@ class Crypt {
 		$this->logger = $logger;
 		$this->user = $userSession && $userSession->isLoggedIn() ? $userSession->getUser() : false;
 		$this->config = $config;
+		$this->supportedKeyFormats = ['hash', 'password'];
 	}
 
 	/**
@@ -161,10 +172,23 @@ class Crypt {
 
 	/**
 	 * generate header for encrypted file
+	 *
+	 * @param string $keyFormat (can be 'hash' or 'password')
+	 * @return string
+	 * @throws \InvalidArgumentException
 	 */
-	public function generateHeader() {
+	public function generateHeader($keyFormat = 'hash') {
+
+		if (in_array($keyFormat, $this->supportedKeyFormats, true) === false) {
+			throw new \InvalidArgumentException('key format "' . $keyFormat . '" is not supported');
+		}
+
 		$cipher = $this->getCipher();
-		$header = self::HEADER_START . ':cipher:' . $cipher . ':' . self::HEADER_END;
+
+		$header = self::HEADER_START
+			. ':cipher:' . $cipher
+			. ':keyFormat:' . $keyFormat
+			. ':' . self::HEADER_END;
 
 		return $header;
 	}
@@ -211,6 +235,25 @@ class Crypt {
 		return $cipher;
 	}
 
+	/**
+	 * get key size depending on the cipher
+	 *
+	 * @param string $cipher supported ('AES-256-CFB' and 'AES-128-CFB')
+	 * @return int
+	 * @throws \InvalidArgumentException
+	 */
+	protected function getKeySize($cipher) {
+		if ($cipher === 'AES-256-CFB') {
+			return 32;
+		} else if ($cipher === 'AES-128-CFB') {
+			return 16;
+		}
+
+		throw new \InvalidArgumentException(
+			'Wrong cipher defined only AES-128-CFB and AES-256-CFB are supported.'
+		);
+	}
+
 	/**
 	 * get legacy cipher
 	 *
@@ -237,6 +280,63 @@ class Crypt {
 		return $data . 'xx';
 	}
 
+	/**
+	 * generate password hash used to encrypt the users private key
+	 *
+	 * @param string $password
+	 * @param string $cipher
+	 * @return string
+	 */
+	protected function generatePasswordHash($password, $cipher) {
+		$instanceId = $this->config->getSystemValue('instanceid');
+		$instanceSecret = $this->config->getSystemValue('secret');
+		$salt = hash('sha256', $instanceId . $instanceSecret, true);
+		$keySize = $this->getKeySize($cipher);
+
+		if (function_exists('hash_pbkdf2')) {
+			$hash = hash_pbkdf2(
+				'sha256',
+				$password,
+				$salt,
+				100000,
+				$keySize,
+				true
+			);
+		} else {
+			// fallback to 3rdparty lib for PHP <= 5.4.
+			// FIXME: Can be removed as soon as support for PHP 5.4 was dropped
+			$fallback = new PBKDF2Fallback();
+			$hash = $fallback->pbkdf2(
+				'sha256',
+				$password,
+				$salt,
+				100000,
+				$keySize,
+				true
+			);
+		}
+
+		return $hash;
+	}
+
+	/**
+	 * encrypt private key
+	 *
+	 * @param string $privateKey
+	 * @param string $password
+	 * @return bool|string
+	 */
+	public function encryptPrivateKey($privateKey, $password) {
+		$cipher = $this->getCipher();
+		$hash = $this->generatePasswordHash($password, $cipher);
+		$encryptedKey = $this->symmetricEncryptFileContent(
+			$privateKey,
+			$hash
+		);
+
+		return $encryptedKey;
+	}
+
 	/**
 	 * @param string $privateKey
 	 * @param string $password
@@ -252,6 +352,16 @@ class Crypt {
 			$cipher = self::LEGACY_CIPHER;
 		}
 
+		if (isset($header['keyFormat'])) {
+			$keyFormat = $header['keyFormat'];
+		} else {
+			$keyFormat = self::LEGACY_KEY_FORMAT;
+		}
+
+		if ($keyFormat === 'hash') {
+			$password = $this->generatePasswordHash($password, $cipher);
+		}
+
 		// If we found a header we need to remove it from the key we want to decrypt
 		if (!empty($header)) {
 			$privateKey = substr($privateKey,
@@ -263,18 +373,29 @@ class Crypt {
 			$password,
 			$cipher);
 
-		// Check if this is a valid private key
+		if ($this->isValidPrivateKey($plainKey) === false) {
+			return false;
+		}
+
+		return $plainKey;
+	}
+
+	/**
+	 * check if it is a valid private key
+	 *
+	 * @param $plainKey
+	 * @return bool
+	 */
+	protected function isValidPrivateKey($plainKey) {
 		$res = openssl_get_privatekey($plainKey);
 		if (is_resource($res)) {
 			$sslInfo = openssl_pkey_get_details($res);
-			if (!isset($sslInfo['key'])) {
-				return false;
+			if (isset($sslInfo['key'])) {
+				return true;
 			}
-		} else {
-			return false;
 		}
 
-		return $plainKey;
+		return true;
 	}
 
 	/**
@@ -358,7 +479,7 @@ class Crypt {
 	 * @param $data
 	 * @return array
 	 */
-	private function parseHeader($data) {
+	protected function parseHeader($data) {
 		$result = [];
 
 		if (substr($data, 0, strlen(self::HEADER_START)) === self::HEADER_START) {
diff --git a/apps/encryption/lib/keymanager.php b/apps/encryption/lib/keymanager.php
index 8c8c1f8fd780710e24a0c7a2ddad020189aea66f..47a8e7d391f35c0c1c2c806e4bb6436d66823f87 100644
--- a/apps/encryption/lib/keymanager.php
+++ b/apps/encryption/lib/keymanager.php
@@ -146,7 +146,7 @@ class KeyManager {
 				Encryption::ID);
 
 			// Encrypt private key empty passphrase
-			$encryptedKey = $this->crypt->symmetricEncryptFileContent($keyPair['privateKey'], '');
+			$encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], '');
 			$header = $this->crypt->generateHeader();
 			$this->setSystemPrivateKey($this->publicShareKeyId, $header . $encryptedKey);
 		}
@@ -203,8 +203,8 @@ class KeyManager {
 		// Save Public Key
 		$this->setPublicKey($uid, $keyPair['publicKey']);
 
-		$encryptedKey = $this->crypt->symmetricEncryptFileContent($keyPair['privateKey'],
-			$password);
+		$encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], $password);
+
 		$header = $this->crypt->generateHeader();
 
 		if ($encryptedKey) {
@@ -226,8 +226,7 @@ class KeyManager {
 			$keyPair['publicKey'],
 			Encryption::ID);
 
-		$encryptedKey = $this->crypt->symmetricEncryptFileContent($keyPair['privateKey'],
-			$password);
+		$encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], $password);
 		$header = $this->crypt->generateHeader();
 
 		if ($encryptedKey) {
diff --git a/apps/encryption/lib/recovery.php b/apps/encryption/lib/recovery.php
index e31a14fba63a8a96b37c425728250df2c545f1e4..e82ecca9d49a64a8ddb9b7b7177d3aef8fe2d4db 100644
--- a/apps/encryption/lib/recovery.php
+++ b/apps/encryption/lib/recovery.php
@@ -136,7 +136,7 @@ class Recovery {
 	public function changeRecoveryKeyPassword($newPassword, $oldPassword) {
 		$recoveryKey = $this->keyManager->getSystemPrivateKey($this->keyManager->getRecoveryKeyId());
 		$decryptedRecoveryKey = $this->crypt->decryptPrivateKey($recoveryKey, $oldPassword);
-		$encryptedRecoveryKey = $this->crypt->symmetricEncryptFileContent($decryptedRecoveryKey, $newPassword);
+		$encryptedRecoveryKey = $this->crypt->encryptPrivateKey($decryptedRecoveryKey, $newPassword);
 		$header = $this->crypt->generateHeader();
 		if ($encryptedRecoveryKey) {
 			$this->keyManager->setSystemPrivateKey($this->keyManager->getRecoveryKeyId(), $header . $encryptedRecoveryKey);
diff --git a/apps/encryption/tests/controller/SettingsControllerTest.php b/apps/encryption/tests/controller/SettingsControllerTest.php
index ed8135b9c4db24044dfc3333cfe2f81bc343535e..d985c7d7d25638f296a34ab2cf3b5576052b3bc9 100644
--- a/apps/encryption/tests/controller/SettingsControllerTest.php
+++ b/apps/encryption/tests/controller/SettingsControllerTest.php
@@ -188,7 +188,7 @@ class SettingsControllerTest extends TestCase {
 
 		$this->cryptMock
 			->expects($this->once())
-			->method('symmetricEncryptFileContent')
+			->method('encryptPrivateKey')
 			->willReturn('encryptedKey');
 
 		$this->cryptMock
diff --git a/apps/encryption/tests/hooks/UserHooksTest.php b/apps/encryption/tests/hooks/UserHooksTest.php
index 921c924d015b5c2f40c86187dc9c212e07e66fa2..aa16a4d87039600fe0b93ebb2f4233d6252c22a0 100644
--- a/apps/encryption/tests/hooks/UserHooksTest.php
+++ b/apps/encryption/tests/hooks/UserHooksTest.php
@@ -114,7 +114,7 @@ class UserHooksTest extends TestCase {
 			->willReturnOnConsecutiveCalls(true, false);
 
 		$this->cryptMock->expects($this->exactly(4))
-			->method('symmetricEncryptFileContent')
+			->method('encryptPrivateKey')
 			->willReturn(true);
 
 		$this->cryptMock->expects($this->any())
diff --git a/apps/encryption/tests/lib/KeyManagerTest.php b/apps/encryption/tests/lib/KeyManagerTest.php
index 0bac5e0341b2749c7be6f004d4768445bf9eb61e..71b00cf254add7e95f4e9927f683abe79d4e78c4 100644
--- a/apps/encryption/tests/lib/KeyManagerTest.php
+++ b/apps/encryption/tests/lib/KeyManagerTest.php
@@ -260,7 +260,7 @@ class KeyManagerTest extends TestCase {
 			->method('setSystemUserKey')
 			->willReturn(true);
 		$this->cryptMock->expects($this->any())
-			->method('symmetricEncryptFileContent')
+			->method('encryptPrivateKey')
 			->with($this->equalTo('privateKey'), $this->equalTo('pass'))
 			->willReturn('decryptedPrivateKey');
 
diff --git a/apps/encryption/tests/lib/RecoveryTest.php b/apps/encryption/tests/lib/RecoveryTest.php
index 8d5d31af0b82196c206cf8d465d279c75351bb12..c0624a36be95006b8887f5d826a04d348dd5305d 100644
--- a/apps/encryption/tests/lib/RecoveryTest.php
+++ b/apps/encryption/tests/lib/RecoveryTest.php
@@ -96,7 +96,7 @@ class RecoveryTest extends TestCase {
 			->method('decryptPrivateKey');
 
 		$this->cryptMock->expects($this->once())
-			->method('symmetricEncryptFileContent')
+			->method('encryptPrivateKey')
 			->willReturn(true);
 
 		$this->assertTrue($this->instance->changeRecoveryKeyPassword('password',
diff --git a/apps/encryption/tests/lib/crypto/cryptTest.php b/apps/encryption/tests/lib/crypto/cryptTest.php
index 14ed1513e1e39d00de0d31d08fb66c78f5657f7b..3c7767a8908256e712ef980569416c121a48ad52 100644
--- a/apps/encryption/tests/lib/crypto/cryptTest.php
+++ b/apps/encryption/tests/lib/crypto/cryptTest.php
@@ -98,18 +98,41 @@ class cryptTest extends TestCase {
 
 
 	/**
-	 * test generateHeader
+	 * test generateHeader with valid key formats
+	 *
+	 * @dataProvider dataTestGenerateHeader
 	 */
-	public function testGenerateHeader() {
+	public function testGenerateHeader($keyFormat, $expected) {
 
 		$this->config->expects($this->once())
 			->method('getSystemValue')
 			->with($this->equalTo('cipher'), $this->equalTo('AES-256-CFB'))
 			->willReturn('AES-128-CFB');
 
-		$this->assertSame('HBEGIN:cipher:AES-128-CFB:HEND',
-			$this->crypt->generateHeader()
-		);
+		if ($keyFormat) {
+			$result = $this->crypt->generateHeader($keyFormat);
+		} else {
+			$result = $this->crypt->generateHeader();
+		}
+
+		$this->assertSame($expected, $result);
+	}
+
+	/**
+	 * test generateHeader with invalid key format
+	 *
+	 * @expectedException \InvalidArgumentException
+	 */
+	public function testGenerateHeaderInvalid() {
+		$this->crypt->generateHeader('unknown');
+	}
+
+	public function dataTestGenerateHeader() {
+		return [
+			[null, 'HBEGIN:cipher:AES-128-CFB:keyFormat:hash:HEND'],
+			['password', 'HBEGIN:cipher:AES-128-CFB:keyFormat:password:HEND'],
+			['hash', 'HBEGIN:cipher:AES-128-CFB:keyFormat:hash:HEND']
+		];
 	}
 
 	/**
@@ -262,4 +285,82 @@ class cryptTest extends TestCase {
 
 	}
 
+	/**
+	 * test return values of valid ciphers
+	 *
+	 * @dataProvider dataTestGetKeySize
+	 */
+	public function testGetKeySize($cipher, $expected) {
+		$result = $this->invokePrivate($this->crypt, 'getKeySize', [$cipher]);
+		$this->assertSame($expected, $result);
+	}
+
+	/**
+	 * test exception if cipher is unknown
+	 *
+	 * @expectedException \InvalidArgumentException
+	 */
+	public function testGetKeySizeFailure() {
+		$this->invokePrivate($this->crypt, 'getKeySize', ['foo']);
+	}
+
+	public function dataTestGetKeySize() {
+		return [
+			['AES-256-CFB', 32],
+			['AES-128-CFB', 16],
+		];
+	}
+
+	/**
+	 * @dataProvider dataTestDecryptPrivateKey
+	 */
+	public function testDecryptPrivateKey($header, $privateKey, $expectedCipher, $isValidKey, $expected) {
+		/** @var \OCA\Encryption\Crypto\Crypt | \PHPUnit_Framework_MockObject_MockObject $crypt */
+		$crypt = $this->getMockBuilder('OCA\Encryption\Crypto\Crypt')
+			->setConstructorArgs(
+				[
+					$this->logger,
+					$this->userSession,
+					$this->config
+				]
+			)
+			->setMethods(
+				[
+					'parseHeader',
+					'generatePasswordHash',
+					'symmetricDecryptFileContent',
+					'isValidPrivateKey'
+				]
+			)
+			->getMock();
+
+		$crypt->expects($this->once())->method('parseHeader')->willReturn($header);
+		if (isset($header['keyFormat']) && $header['keyFormat'] === 'hash') {
+			$crypt->expects($this->once())->method('generatePasswordHash')->willReturn('hash');
+			$password = 'hash';
+		} else {
+			$crypt->expects($this->never())->method('generatePasswordHash');
+			$password = 'password';
+		}
+
+		$crypt->expects($this->once())->method('symmetricDecryptFileContent')
+			->with('privateKey', $password, $expectedCipher)->willReturn('key');
+		$crypt->expects($this->once())->method('isValidPrivateKey')->willReturn($isValidKey);
+
+		$result = $crypt->decryptPrivateKey($privateKey, 'password');
+
+		$this->assertSame($expected, $result);
+	}
+
+	public function dataTestDecryptPrivateKey() {
+		return [
+			[['cipher' => 'AES-128-CFB', 'keyFormat' => 'password'], 'HBEGIN:HENDprivateKey', 'AES-128-CFB', true, 'key'],
+			[['cipher' => 'AES-256-CFB', 'keyFormat' => 'password'], 'HBEGIN:HENDprivateKey', 'AES-256-CFB', true, 'key'],
+			[['cipher' => 'AES-256-CFB', 'keyFormat' => 'password'], 'HBEGIN:HENDprivateKey', 'AES-256-CFB', false, false],
+			[['cipher' => 'AES-256-CFB', 'keyFormat' => 'hash'], 'HBEGIN:HENDprivateKey', 'AES-256-CFB', true, 'key'],
+			[['cipher' => 'AES-256-CFB'], 'HBEGIN:HENDprivateKey', 'AES-256-CFB', true, 'key'],
+			[[], 'privateKey', 'AES-128-CFB', true, 'key'],
+		];
+	}
+
 }
diff --git a/apps/encryption/vendor/pbkdf2fallback.php b/apps/encryption/vendor/pbkdf2fallback.php
new file mode 100644
index 0000000000000000000000000000000000000000..ca579f8e7dc29d8ca208174992756c149d30adb1
--- /dev/null
+++ b/apps/encryption/vendor/pbkdf2fallback.php
@@ -0,0 +1,87 @@
+<?php
+/* Note; This class can be removed as soon as we drop PHP 5.4 support.
+ *
+ *
+ * Password Hashing With PBKDF2 (http://crackstation.net/hashing-security.htm).
+ * Copyright (c) 2013, Taylor Hornby
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace OCA\Encryption\Vendor;
+
+class PBKDF2Fallback {
+
+	/*
+	 * PBKDF2 key derivation function as defined by RSA's PKCS #5: https://www.ietf.org/rfc/rfc2898.txt
+	 * $algorithm - The hash algorithm to use. Recommended: SHA256
+	 * $password - The password.
+	 * $salt - A salt that is unique to the password.
+	 * $count - Iteration count. Higher is better, but slower. Recommended: At least 1000.
+	 * $key_length - The length of the derived key in bytes.
+	 * $raw_output - If true, the key is returned in raw binary format. Hex encoded otherwise.
+	 * Returns: A $key_length-byte key derived from the password and salt.
+	 *
+	 * Test vectors can be found here: https://www.ietf.org/rfc/rfc6070.txt
+	 *
+	 * This implementation of PBKDF2 was originally created by https://defuse.ca
+	 * With improvements by http://www.variations-of-shadow.com
+	 */
+	public function pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false) {
+		$algorithm = strtolower($algorithm);
+		if (!in_array($algorithm, hash_algos(), true))
+			trigger_error('PBKDF2 ERROR: Invalid hash algorithm.', E_USER_ERROR);
+		if ($count <= 0 || $key_length <= 0)
+			trigger_error('PBKDF2 ERROR: Invalid parameters.', E_USER_ERROR);
+
+		if (function_exists("hash_pbkdf2")) {
+			// The output length is in NIBBLES (4-bits) if $raw_output is false!
+			if (!$raw_output) {
+				$key_length = $key_length * 2;
+			}
+			return hash_pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output);
+		}
+
+		$hash_length = strlen(hash($algorithm, "", true));
+		$block_count = ceil($key_length / $hash_length);
+
+		$output = "";
+		for ($i = 1; $i <= $block_count; $i++) {
+			// $i encoded as 4 bytes, big endian.
+			$last = $salt . pack("N", $i);
+			// first iteration
+			$last = $xorsum = hash_hmac($algorithm, $last, $password, true);
+			// perform the other $count - 1 iterations
+			for ($j = 1; $j < $count; $j++) {
+				$xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true));
+			}
+			$output .= $xorsum;
+		}
+
+		if ($raw_output)
+			return substr($output, 0, $key_length);
+		else
+			return bin2hex(substr($output, 0, $key_length));
+	}
+}