/** * @see phpsecStore::read() */ public function read($type, $id) { /* The first part is prettu basic. Get stuff from databse. */ $sth = $this->dbh->prepare('SELECT * FROM ' . $this->table . ' WHERE type = :type AND id = :id LIMIT 1'); $data = array('id' => $id, 'type' => $type); $sth->execute($data); $data = $sth->fetch(PDO::FETCH_ASSOC); if ($data === false) { return false; } /* Calculate expected MAC. */ $mac = phpsecCrypt::pbkdf2($data['data'], $id, 1000, 32); /* Compare MAC */ if ($mac != $data['mac']) { phpsec::error('Message authentication code invalid while reading store'); return false; } /* And success! */ return unserialize($data['data']); }
/** * @see phpsecStore::write() */ public function write($type, $id, $data) { $fileName = $this->fileName($type, $id); $data = serialize($data); $saveData['id'] = base64_encode($id); $saveData['mac'] = base64_encode(phpsecCrypt::pbkdf2($data, $id, 1000, 32)); $saveData['time'] = time(); $jsonData = json_encode($saveData); $fp = fopen($fileName, 'w'); if ($fp !== false) { if (flock($fp, LOCK_EX)) { fwrite($fp, $jsonData . "\n\n"); fwrite($fp, $data); flock($fp, LOCK_UN); fclose($fp); return true; } else { phpsec::error('Could not lock file while writing to store'); } } return false; }
/** * Validate a user-supplied password against a stored password generated * using the phpsecPw::hash() method. * * @param string $password * The password supplied by the user in the login form. * * @param string $dbPassword * The json string fetched from the database, in the exact format * as created by phpsecPw::hash(). * * @return boolean * True on password match, false otherwise. */ public static function check($password, $dbPassword) { /** * Unserialize registered password array and validate it to ensure * we got a valid array. */ $data = json_decode($dbPassword, true); $dataStructure = array('hash' => true, 'salt' => true, 'algo' => true); /* Check structure of array. */ if ($data !== null && phpsec::arrayCheck($data, $dataStructure)) { /* Try to Base64 decode the salt. base64_decode() will return false * if the string passed is not Base64 encoded. This way we can separate * binary salts from the old type of salts. */ $decodedSalt = base64_decode($data['salt'], true); if ($decodedSalt !== false) { /* The salt was Base64 encoded. Use the decoded version. */ $data['salt'] = $decodedSalt; } /** * We do a switch on the 6 first characters on the used hashing method. * This way we are able to catch when pbkdf2 is used, since this has * it's iteration count, derived key length and PRF attached to it: * "pbkdf2:iteration count:derived key length:PRF" */ switch (substr($data['algo'], 0, 6)) { case self::phpsecPw_PBKDF2: /* As described above, we need to seperate out the iteration count * and derived key length. */ list($method, $iterationCount, $dkLen, $prf) = explode(':', $data['algo']); /* Just to make sure anything fishy isn't going on. */ if (!is_numeric($iterationCount) || !is_numeric($dkLen)) { return false; } /* Create a new derived key, with the iteration count and derived key length * that were used when generating the original dk. */ $dk = phpsecCrypt::pbkdf2($password, $data['salt'], $iterationCount, $dkLen, $prf); /* Check the new dk against the old base64 encoded dk. */ if ($dk === base64_decode($data['hash'])) { return true; } break; default: /* If not pbkdf2, we assume normal hash. */ $pwInjected = self::inject($password, $data['salt']); /* Create a hash and see if it matches. */ if (hash($data['algo'], $pwInjected) == $data['hash']) { return true; } } } else { /* Invalid array supplied. */ phpsec::error('Invalid data supplied. Expected serialized array as returned by pwHash()'); } return false; }
/** * Initialize the library. * This method actually does several things: * 1. Registers the phpsec::load() method as autoload function. * 2. Opens the store. (connect to database etc.) * 3. Set charset for multibyte functions. * 4. Enable the phpSec session handler and starts a session (if session handler is enabled). * 5. Set's or get a UID that is used by phpsecCache. * 6. Decrypts cookies (if enabled). * * If no DSN (storage method) is configured. This method will stop after completing step 1. * * @return bool true * Will always return true. */ public static function init() { /* First of all, register the autoloading function. * If we have one set from somewhere else, keep it. */ $autoLoadFunctions = spl_autoload_functions(); $autoLoadFunctions[] = 'phpsec::load'; foreach ($autoLoadFunctions as $autoLoadFunction) { spl_autoload_register($autoLoadFunction); } /* Autoloader all good to go. If we don't have a storage set * we can skip the rest of this method. */ if (self::$_dsn === null) { return true; } /* Open store. */ list($storeType, $storeDest) = explode(':', self::$_dsn); switch ($storeType) { case 'filesystem': self::$store = new phpsecStoreFilesystem($storeDest); break; case 'mysql': self::$store = new phpsecStorePdo($storeDest); break; default: self::error('Store type(' . $storeType . ') invalid', E_USER_ERROR); } /* Set the charset of the multibyte functions in PHP. */ mb_internal_encoding(self::$_charset); mb_regex_encoding(self::$_charset); /* Enable the custom session handler if enabled. */ if (self::$_sessenable === true) { ini_set('session.save_handler', 'user'); session_set_save_handler('phpsecSession::open', 'phpsecSession::close', 'phpsecSession::read', 'phpsecSession::write', 'phpsecSession::destroy', 'phpsecSession::gc'); /* Since we set a session cookie on our session handler, disable the built-in cookies. */ ini_set('session.use_cookies', 0); /* Start a new session. */ session_start(); /* Check the fingerprint to see if it matches, if not clear session data. */ $fingerprint = hash(self::HASH_TYPE, 'phpSec-fingerprint' . $_SERVER['HTTP_USER_AGENT']); if (!isset($_SESSION['phpSec-fingerprint'])) { $_SESSION['phpSec-fingerprint'] = $fingerprint; } if ($fingerprint != $_SESSION['phpSec-fingerprint']) { $_SESSION = array(); } } /* Create a random token for each visitor and store it the users session. This is for example used to identify owners of cache data. */ if (!isset($_SESSION['phpSec-uid'])) { self::$uid = self::genUid(); $_SESSION['phpSec-uid'] = self::$uid; } else { self::$uid = $_SESSION['phpSec-uid']; } /* If the phpSec secure cookie monster is enabled detect encrypted cookies. */ if (self::$_cookieenable === true) { phpsecCookie::detect(); } return true; }
/** * XSS filter. Returns a string that is safe to use on the page. * * There are three modes: * strip: HTML is stripped from the string * before it is inserted. * escapeAll: HTML and special characters is escaped from the string * before it is inserted. * escape: Only HTML is escaped from the string. Special characters * is kept as is. * url: Encode a string according to RFC 3986 for use in a URL. * * @see https://phpseclib.com/manual/filter * @see http://www.faqs.org/rfcs/rfc3986 * * @param string $str * String to filter * * @param string $mode * String defining what filter to apply. */ public static function f($str, $mode = 'escape') { switch ($mode) { case 'strip': /* HTML tags are stripped from the string before it is used. */ return strip_tags($str); case 'escapeAll': /* HTML and special characters are escaped from the string before it is used. */ return htmlentities($str, ENT_QUOTES, phpSec::$_charset); case 'escape': /* Only HTML tags are escaped from the string. Special characters is kept as is. */ return htmlspecialchars($str, ENT_NOQUOTES, phpSec::$_charset); case 'url': /* Encode a string according to RFC 3986 for use in a URL. */ return rawurlencode($str); default: phpsec::error('Unknown variable type', E_USER_NOTICE); } }
/** * Implement PBKDF2 as described in RFC 2898. * * @param string $p * Password to protect. * * @param string $s * Salt. * * @param integer $c * Iteration count. * * @param integer $dkLen * Derived key length. * * @param string $a * A hash algorithm. * * @return binary * Derived key. */ public static function pbkdf2($p, $s, $c, $dkLen, $a = 'sha256') { $hLen = strlen(hash($a, null, true)); /* Hash length. */ $l = ceil($dkLen / $hLen); /* Length in blocks of derived key. */ $dk = ''; /* Derived key. */ /* Step 1. Check dkLen. */ if ($dkLen > (2 ^ 32 - 1) * $hLen) { phpsec::error('Derived key too long'); return false; } for ($block = 1; $block <= $l; $block++) { /* Initial hash for this block. */ $ib = $b = hash_hmac($a, $s . pack('N', $block), $p, true); /* Do block iterations. */ for ($i = 1; $i < $c; $i++) { /* XOR iteration. */ $ib ^= $b = hash_hmac($a, $b, $p, true); } /* Append iterated block. */ $dk .= $ib; } /* Returned derived key. */ return substr($dk, 0, $dkLen); }