Skip to content

Instantly share code, notes, and snippets.

@rk rk/pcrypt.php
Created Aug 10, 2009

What would you like to do?
A variant upon crypt() and phpass that isn't interested in portability, so much as customized security for PHP 5.1.2 or higher implementations.
PHP Crypt 0.1
Original Source:
Designed for password protection. Provides some extent of customization over
crypt, bcrypt, and phpass but shares similar methods. PCrypt is not compatible
with those implementations.
Placed in the public domain by Robert Kosek on August 10th, 2009. When you edit
this, please change the version number.
SHA512 / WHIRLPOOL @ 500 rounds average 0.03 seconds to hash a given password on
a system with: 4gb RAM & an Intel Core2 Duo @ 2GHz
This limits attacks to 33/per-second. Increasing rounds to 5,000 involves a
0.124 average time and potentially 5 tests per second.
* PHP 5.1.2 or higher, w/ HASH message digest framework
abstract class pcrypt {
// Used for the rotating salt value; should still be a strong algorithm, but
// not sha512 or whirlpool.
static $salt_algorithm = 'gost';
// Used for the password _only_, should be the strongest algorithm available
// like sha512 or whirlpool.
static $hash_algorithm = 'sha512';
// When the number of rounds is a valid multiple of this number ($i % $n == 0)
// the below callback is called; the callback must take 1 number and return
// something castable into a string.
static $multiple_count = 13;
static $multiple_callback = 'sqrt';
// Hashes a password with a random salt value, returns in the format:
// salt$base64-password-hash
static public function hash_password($password, $rounds = 4000) {
return self::crypt($password, hash(self::$salt_algorithm, uniqid('', true)),
// Verifies a plaintext password against the stored hash previously generated,
// returns boolean.
static public function verify_password($password, $stored, $rounds = 4000) {
$parts = explode('$', $stored);
$salt = $parts[0];
$test = self::crypt($password, $salt, $rounds);
return strcmp($stored, $test) === 0;
static private function crypt($password, $beginning_salt, $rounds) {
$func = self::$multiple_callback;
$salt = $beginning_salt;
$result = hash_hmac(self::$hash_algorithm, $password, $salt, true);
do {
if($rounds % self::$multiple_count === 0) {
$salt = hash(self::$salt_algorithm, $func($rounds) . $salt, true);
} else {
$salt = hash(self::$salt_algorithm, $salt . $beginning_salt, true);
$result = hash_hmac(self::$hash_algorithm, $result, $salt, true);
} while($rounds-- > 0);
return $beginning_salt . '$' . base64_encode($result);
if(!defined('ENVIRONMENT') || ENVIRONMENT !== 'production') {
echo '<pre style="font: 10pt consolas;">';
echo "<u>testing hashing (default)</u>\n";
echo "testing password: <b>admin</b>\n";
$start = microtime(true);
$hash = pcrypt::hash_password('admin');
$time = round(microtime(true) - $start, 7);
echo "resulting hash: <b>$hash</b>\n";
echo "elapsed time: <b>$time</b>\n";
echo 'resulting length: <b>', strlen($hash), "</b>\n";
echo 'verifies: <b>';
echo pcrypt::verify_password('admin', $hash) ? 'true' : 'false', "</b>\n";
// test with customized & pseudo-random methods...
function __my_really_special_func($i) {
static $base_seed;
if(empty($base_seed)) { $base_seed = time() + log($i, 8); }
mt_srand($base_seed + $i);
return mt_rand($i, mt_getrandmax());
echo "\n<u>testing hashing (custom)</u>\n";
echo "testing password: <b>admin</b>\n";
pcrypt::$salt_algorithm = 'tiger192,4';
pcrypt::$multiple_callback = '__my_really_special_func';
$hash = pcrypt::hash_password('admin');
echo "resulting hash: <b>$hash</b>\n";
echo 'resulting length: <b>', strlen($hash), "</b>\n";
echo 'verifies: <b>';
echo pcrypt::verify_password('admin', $hash) ? 'true' : 'false', "</b>\n";
echo '</pre>';
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.