Jon-G blogs for Net-Entwicklung.de

17.06.2012

Hashing passwords and other data in PHP… the ultimate hashing class? ;)

Filed under: Coding — Jonathan Gilbert @ 01:00

Here is a pretty robust and easy to use class to encrpty passwords. Actually we should call it hashing, not encryption because encryption implies that decryption is possible. With one-way hashes decryption is not possible. To use it, just create a subclass of the abstract class (example below) where you optionally set a hashing algorithm. If you don’t set it, the most secure algorithm gets chosen automatically. Instantiate the class and call createHash to create a hash:

    $hashObj = new MyHash();
    $hash = $hashObj->createHash($data);

and to verify a hash, use verifyHash:

    $hashObj = new MyHash();
    $verified = $hashObj->verifyHash($data, $hash);

You can verify hashes created with different algorithms and, because the data is serialized, you can hash and verify any variable type, not just strings as most hashing classes do.

Any thoughts? Improvements? Disadvantages? Errors?

/**
 * Hashing
 * @author jon.gilbert@net-entwicklung.de
 */
abstract class AbstractHash
{
    const HASH_METHOD_MD5    = 'md5';
    const HASH_METHOD_SHA256 = 'sha256';
    const HASH_METHOD_SHA512 = 'sha512';
    const HASH_METHOD_BLOWFISH = 'blowfish';

    const SALT_LENGTH_MD5      = 12;
    const SALT_LENGTH_SHA256   = 16;
    const SALT_LENGTH_SHA512   = 16;
    const SALT_LENGTH_BLOWFISH = 22;

    protected $minIterationsSha256 = 1000;
    protected $maxIterationsSha256 = 50000;  // absolute maximum 999999999

    protected $minIterationsSha512 = 1000;
    protected $maxIterationsSha512 = 50000;  // absolute maximum 999999999

    protected $minIterationsBlowfish = 4;
    protected $maxIterationsBlowfish = 14;  // absolute maximum 31

    protected $minIterationsMd5 = 1;
    protected $maxIterationsMd5 = 1;  // absolute maximum 31

    /**
     * @var string - hash method to be used (one of HASH_METHOD_ constants)
     */
    private $hashMethod = null;

    /**
     * @var int - number of iterations
     */
    private $iterations = null;

    private $minIterations = null;
    private $maxIterations = null;
    private $saltLength    = null;

    /**
     * verify that a hash is correct
     *
     * @param mixed  $data
     * @param string $hash
     * @throws \Exception if hash not correct
     * @return true
     */
    public function verifyHash($data, $hash)
    {
        $salt = $this->getSaltFromHashed($hash);
        return $this->verifyHashWithSalt($data, $salt, $hash);
    }

    /**
     * extract salt from a given hashed string
     * @param string $hashed
     * @return string
     */
    protected function getSaltFromHashed($hashed)
    {
        // test for blowfisch
        $pattern = '/^(\$2a\$[0-3][0-9]\$.{' . self::SALT_LENGTH_BLOWFISH . '}).*$/';
        preg_match($pattern, $hashed, $matches);
        if (isset($matches[1]))
        {
            $this->setHashMethod(self::HASH_METHOD_BLOWFISH);
            $salt = $matches[1];
            return $salt;
        }

        // test for SHA512
        $pattern = '/^(\$6\$rounds=[0-9]+\$)(.{' . self::SALT_LENGTH_SHA512 . '}\$).*$/';
        preg_match($pattern, $hashed, $matches);
        if (isset($matches[2]))
        {
            $salt = $matches[1] . $matches[2];
            $this->setHashMethod(self::HASH_METHOD_SHA512);
            return $salt;
        }

        // test for SHA256
        $pattern = '/^(\$5\$rounds=[0-9]+\$)(.{' . self::SALT_LENGTH_SHA256 . '}\$).*$/';
        preg_match($pattern, $hashed, $matches);
        if (isset($matches[2]))
        {
            $salt = $matches[1] . $matches[2];
            $this->setHashMethod(self::HASH_METHOD_SHA256);
            return $salt;
        }

        // test for MD5
        $pattern = '/^(\$1\$)(.{' . self::SALT_LENGTH_MD5 . '}).+$/';
        preg_match($pattern, $hashed, $matches);
        if (isset($matches[2]))
        {
            $this->setHashMethod(self::HASH_METHOD_MD5);
            $salt = $matches[1] . $matches[2];
            return $salt;
        }

        throw new Exception('Invalid hash');
    }

    /**
     * create a hash value
     *
     * @param mixed $data
     * @return string
     */
    public function createHash($data)
    {
        $randomSalt   = $this->createRandomSalt();
        switch ($this->hashMethod)
        {
            case self::HASH_METHOD_BLOWFISH:
                $salt = '$2a$' . sprintf('%1$02d', $this->iterations) . '$';
                break;
            case self::HASH_METHOD_SHA256:
                $salt = '$5$rounds=' . $this->iterations . '$';
                break;
            case self::HASH_METHOD_SHA512:
                $salt = '$6$rounds=' . $this->iterations . '$';
                break;
            case self::HASH_METHOD_MD5:
                $salt = '$1$';
                break;
        }

        $tempSalt = str_shuffle($randomSalt);
        $salt .= substr($tempSalt,0, $this->saltLength);

        $dataString = serialize($data);
        return $this->createHashWithSalt($dataString, $salt);
    }

    /**
     * initialise hashing
     * @return void
     */
    final public function __construct()
    {
        $this->customInit();    // optionally overwrite $this->hashMethod
        $this->setHashMethod($this->hashMethod);
        $this->setIterations($this->iterations);
    }

    /**
     * set number of iterations
     * @param int $iterations
     * @return void
     */
    public function setIterations($iterations = null)
    {
        if (null !== $iterations)
        {
            // ensure iterations in correct range
            if ($iterations < $this->minIterations || $iterations > $this->maxIterations)
            {
                $iterations = null;
            }
        }
        if (null === $iterations)
        {
            $iterations = $this->getRandomIterations();
        }
        $this->iterations = $iterations;
    }

    /**
     * get random number of iterations depending on hash method
     * @return int
     */
    protected function getRandomIterations()
    {
        $iterations = rand($this->minIterations, $this->maxIterations);
        return $iterations;
    }

    /**
     * initialise hashMethod:
     * should call setHashMethod() with the method to use (or do nothing to keep default)
     * use a HASH_METHOD_ constant as parameter to setHashMethod()
     * may also overwrite  maxIterations* variables
     *
     * @return void
     */
    abstract protected function customInit();

    /**
     * check whether a particular method is supported by system
     * @param string $method
     * @return bool
     */
    protected function methodExists($method)
    {
        switch ($method)
        {
            case self::HASH_METHOD_BLOWFISH:
                if (defined(CRYPT_BLOWFISH) && CRYPT_BLOWFISH == 1)
                {
                    return true;
                }
                break;
            case self::HASH_METHOD_SHA512:
                if (defined(CRYPT_SHA512) && CRYPT_SHA512 == 1)
                {
                    return true;
                }
                break;
            case self::HASH_METHOD_SHA256:
                if (defined(CRYPT_SHA256) && CRYPT_SHA256 == 1)
                {
                    return true;
                }
                break;
            case self::HASH_METHOD_MD5:
                if (defined(CRYPT_MD5) && CRYPT_MD5 == 1)
                {
                    return true;
                }
                break;
            return false;
        }
    }

    /**
     * get best nethod supported by system
     * @return string
     */
    protected function getBestMethod()
    {
        if (defined('CRYPT_BLOWFISH') && CRYPT_BLOWFISH == 1)
        {
            return self::HASH_METHOD_BLOWFISH;
        }
        if (defined('CRYPT_SHA512') && CRYPT_SHA512 == 1)
        {
            return self::HASH_METHOD_SHA512;
        }
        if (defined('CRYPT_SHA256') && CRYPT_SHA266 == 1)
        {
            return self::HASH_METHOD_SHA256;
        }
        if (defined('CRYPT_MD5') && CRYPT_MD5 == 1)
        {
            return self::HASH_METHOD_MD5;
        }
        throw new \Exception('No encryption method available');
    }

    /**
     * set hashMethod
     * @return void
     */
    protected function setHashMethod($method)
    {
        if (false === $this->methodExists($method))
        {
            $this->hashMethod = $this->getBestMethod();
        }
        else
        {
            $this->hashMethod = $method;
        }
        switch ($this->hashMethod)
        {
            case self::HASH_METHOD_BLOWFISH:
                $this->maxIterations = $this->maxIterationsBlowfish;
                $this->minIterations = $this->minIterationsBlowfish;
                $this->saltLength = self::SALT_LENGTH_BLOWFISH;
                break;
            case self::HASH_METHOD_SHA256:
                $this->maxIterations = $this->maxIterationsSha256;
                $this->minIterations = $this->minIterationsSha256;
                $this->saltLength = self::SALT_LENGTH_SHA256;
                break;
            case self::HASH_METHOD_SHA512:
                $this->maxIterations = $this->maxIterationsSha512;
                $this->minIterations = $this->minIterationsSha512;
                $this->saltLength = self::SALT_LENGTH_SHA512;
                break;
            case self::HASH_METHOD_MD5:
            default:
                $this->maxIterations = $this->maxIterationsMd5;
                $this->minIterations = $this->minIterationsMd5;
                $this->saltLength = self::SALT_LENGTH_MD5;
                break;
        }
    }

    /**
     * verify that a hash is correct
     *
     * @param mixed $data
     * @param string $hash
     * @throws \Exception if hash not correct
     * @return true
     */
    private function verifyHashWithSalt($data, $salt, $hash)
    {
        $dataString = serialize($data);
        $calculatedHash = $this->createHashWithSalt($dataString, $salt);
        if ($calculatedHash != $hash)
        {
            return false;
            throw new \Exception('Data could not be verified with hash.');
        }
        return true;
    }

    /**
     * create hash value
     * @param string $dataString
     * @param string $salt
     * @return string
     */
    private function createHashWithSalt($dataString, $salt)
    {
        $hash = crypt($dataString, $salt);
        return $hash;
    }

    /**
     * create a random salt
     * @return string
     */
    private function createRandomSalt()
    {
        $saltChars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
        $randomSalt = '' ;
        $length = rand(50, 100);
        for ($i = 0; $i < $length; $i++)
        {
            $tmpStr = str_shuffle($saltChars);
            $randomSalt .= $tmpStr[0];
        }
        return $randomSalt;
    }

}

########

/**
* MyHash Class
*/
class MyHash extends AbstractHash
{
    /**
     * set the hashing  method
     * @return void
     */
    protected function customInit()
    {
        //  $this->setHashMethod(self::HASH_METHOD_SHA256);
        $this->maxIterationsBlowfish = 5;
    }
}

$data = new stdClass();
$data->param1 = 'param1';
$data->param2 = array('arr1' => 1, 'arr2' => 2);

$hasher = new MyHash();

$hash = $hasher->createHash($data);
$verify = $hasher->verifyHash($data, $hash);

// lets see what we created..
var_dump($data, $hash, $verify);

03.10.2009

IE Testing

Filed under: Coding — Schlagworte: , — Jonathan Gilbert @ 18:15

Testing web applications with Internet Explorer was always a daunting task. Back in the days of IE3, IE4, IE5 and IE5.5 for Mac it was quite normal to have different PCs running different versions because IE never really had a viable option for installing different versions parallel. There was a trick on upgrading from IE4 to 5 which allowed this in theory but each version still tended to work a little different to standalone installations.

Nowadays things are easier – it is quite easy have different configurations running in virtual machines. There are however some other tools kicking around which also (supposedly?) make things easier. Here a a few links I´ve collected (but not necessarily tried):
http://en.gibney.org/crossbrowser_testing/
http://www.my-debugbar.com/wiki/IETester/HomePage “IETester is a free WebBrowser that allows you to have the rendering and javascript engines of IE8, IE7 IE 6 and IE5.5 on Windows 7, Vista and XP, as well as the installed IE in the same process.”

http://www.microsoft.com/downloads/details.aspx?FamilyId=21EABB90-958F-4B64- B5F1-73D0A413C8EF&displaylang=en#filelist VirtualPC direct from Microsoft

06.08.2009

This is amazing! It really works!

Filed under: Fun stuff — Jonathan Gilbert @ 08:29

amazing

15.07.2009

Interesting reads (PHP & more) and tools (Twitter & more)

Filed under: Coding, Databases, Tools — Jonathan Gilbert @ 17:38

More on PHP Performance
http://php100.wordpress.com/2009/07/13/php-performance/

Benchmarking PHP
http://blueprint.intereactive.net/benchmarking-our-php/

PHP Abstract Podcast Episode 42: Keith Casey & Web2Project
http://devzone.zend.com/article/4831-PHP-Abstract-Podcast-Episode-42-Keith-Casey-Web2Project

3 Part Series on namespaces in PHP 5.3
http://www.sitepoint.com/blogs/2009/07/13/php-53-namespaces-basics/
http://www.sitepoint.com/blogs/2009/07/14/php-namespaces-import-alias-resolution/
http://www.sitepoint.com/blogs/2009/07/15/how-to-use-php-namespaces-part-3-keywords-and-autoloading/

50 useful PHP Tools
http://www.smashingmagazine.com/2009/01/20/50-extremely-useful-php-tools/

List of “White Label” or “Private Label” (Applications you can Rebrand) Social Networking Platforms, Community Platforms
http://www.web-strategist.com/blog/2007/02/12/list-of-white-label-social-networking-platforms/

Freitags Open House für Freiberufler, Ideen und Projekte
http://hallenprojekt.de/mindmatters

The “AddToAny”-widgets
http://www.addtoany.com/

Twitter Tool for Team Tweeting
http://www.tweetfunnel.com/

Twitter toolbox
http://hootsuite.com/

Widgets galore
http://www.widgetbox.com/

Command line access to the web
http://yubnub.org/

Online wordprocessor
http://writer.zoho.com/

“Test everything” – nice idea to generate links to various services
http://tester.jonasjohn.de/

UIzard – in-browser web appication development tool – want to look more closely at this
http://www.uizard.org/

14.07.2009

Interesting stuff recently read

Filed under: Allgemein — Jonathan Gilbert @ 01:04

45 Incredibly Useful Web Design Checklists and Questionnaires

5 social networking tips for busy businesspeople

Ten unconventional wisdoms for funding startups

101 Essential Freelancing Resources (not all relevant in europe resp. germany but still some interesting links here)

Another interesting YQL feature: XML with callback (JSON-P-X)

‘Give It Away And Pray’ Isn’t A Business Model… But It Doesn’t Mean That ‘Free’ Doesn’t Work

And just for fun:

26.06.2009

Check / Uncheck all checkboxes

Filed under: Coding — Schlagworte: — Jonathan Gilbert @ 20:35

It’s a common problem: you have a list of elements on a page and you want yout user to be able to do something to several or all elements at once (delete, move, etc.) so you give each element a checkbox and a “delete selected” button (or similar :~). Everything is cool but then you figure it would be nice to have a “select all” resp. “unselect all” box too, so help avoid “click orgies”. So in the page you generate something like this:

<input type=’checkbox’ id=’sel1′ value=’xxx’ name=’sel[]’ /> xxx
<input type=’checkbox’ id=’sel2′ value=’yyy’ name=’sel[]’ /> yyy
<input type=’checkbox’ id=’sel3′ value=’zzz’ name=’sel[]’ />  zzz
<input type=’checkbox’ id=’selAll’ value=” name=’selAll’ onClick=’selectAll(this.checked, \”sel[]\”)’ /> (un)select all

There are several ways of doing this, but I like my solution, just because its mine its short and effective:

function selectAll(state, name) {
..boxes = document.getElementsByName(name);
..boxCnt = boxes.length;
..for (i=0; i<boxCnt; i++) {
....boxes[i].checked = state;
..}
}

That’s it!

24.06.2009

Multiple Inserts in Oracle (example using PHP)

Filed under: Coding, Databases — Schlagworte: , , — Jonathan Gilbert @ 00:17

Everybody knows how to make multiple inserts into MySQL:

INSERT INTO [table_name] ([col_1], [col_2])
  VALUES
    ([val_1_1], [val_1_2]),
    ([val_2_1], [val_2_2]),
    ([val_3_1], [val_3_2])

That´s easy! But multiple inserts to an Oracle DB don´t work like that… here are two ways of doing the same job with Oracle:

insert all
  into [table_name] ([col1], [col2]) values ([val1_1], [val1_2])
  into [table_name] ([col1], [col2]) values ([val2_1], [val2_2])
  into [table_name] ([col1], [col2]) values ([val3_1], [val3_2])
  select 1 from dual

or alternatively

insert into [table_name] ([col1], [col2])
  select [val1_1], [val1_2] from dual
  union all
    select [val2_1], [val2_2] from dual
  union all
    select [val3_1], [val3_2] from dual

This is useful if you need to insert a lot of rows into a table – which usually occurs in a job like data import from other sources rather than as a result of user interaction. Running a PHP script for example to insert 50.000 rows into a table would fire 50.000 inserts if you don´t use multiple inserts. This is slow and can also cause you to run into memory problems.

Here is a simple example of how to do this in PHP:

class MyImportTest extends MyDatabaseModel
{
 protected static $_parallelImports = 50;
 protected static $_insertCache = array();

 public static function insertImportDataIntoDb($inputData)
 {
  $added = 0;
  foreach ($inputdata as $inputArray) {
    $added += self::_addCaseToInsertCache($inputArray['col1'], $inputArray['col2']);
    if ($added % self::$_parallelInserts == 0) {
      self::_flushInsertCache();
    }
  }
	if ($added % self::$_parallelInserts != 0) {
    self::_flushInsertCache();
  }
 }

 function addCaseToInsertCache($col1, $col2)
 {
  // maybe do validity checks here and return 0 if an element is not added
  self::$_insertCache[] = array($col1, $col2);
  return 1;
 }

 function flushInsertCache()
 {
  $db = self::getDb();
  $sql = 'INSERT INTO "TABLE_NAME" ("COL1", "COL2")';
  $values = '';
  foreach (self::$_insertCache as $insert) {
            $values .= (empty($values)) ? '' : ' UNION ALL ';
            $values .= 'SELECT ' .
                $insert[0] . ', ' .
                $insert[1] . ', ' .
                $insert[5] . ' FROM DUAL';
        } // if using string values, then don't forget to use quotes!
        $db->query($sql . $values);
        self::$_insertCache = array();
    }
 }
}

23.06.2009

Filed under: Dit un dat, Fun stuff — Schlagworte: , — Jonathan Gilbert @ 23:40

Studieren in Fernost gewinnt Gold. Von Januar bis März 2009 hatte ich das Glück an der Entwicklung der Studiensuchmaschine und der Integration in SchülerVZ arbeiten zu dürfen :-)

Wer nicht in SchülerVZ drin ist geht auf http://www.studieren-in-fernost.de und klickt oben rechts auf “Maschine starten”…

16.06.2009

HowTo: Check if a username exists in LDAP (without authenticating user)

Filed under: Coding — Jonathan Gilbert @ 21:40


/**
* check if user exists in LDAP
* @param string $username username
* @return bool
*/
public static function isUser($username)
{
  $auth = Zend_Auth::getInstance();
  $config = Zend_Registry::get('ldap');
  $options = $config->toArray();

  $password = 'x';

  $adapter = new Zend_Auth_Adapter_Ldap($options, $username, $password);

  // because we have given an invalid password, this will always return error messages
  $result = $auth->authenticate($adapter);
  // here is a sample error message from which we deduce that "data 525" means that a user does not exist
  // "0x31: Invalid credentials: 80090308: LdapErr: DSID-0C090334, comment: AcceptSecurityContext error, data 525, vece: ..."
  // this seems to be confirmed by the following error codes for MS AD found on http://www-01.ibm.com/support/docview.wss?uid=swg21290631
  // 525 user not found
  // 52e invalid credentials
  // 530 not permitted to logon at this time
  // 531 not permitted to logon at this workstation
  // 532 password expired
  // 533 account disabled
  // 701 account expired
  // 773 user must reset password
  // 775 user account locked
  $messages = $result->getMessages();
  if ((false !== strpos($messages[1], 'data 525')) ||
    (false !== strpos($messages[1], 'data 533')) ||
    (false !== strpos($messages[1], 'data 701'))) {
    return false;
  }
  return true;
}

Easy way to delete duplicate rows in Oracle

Filed under: Databases — Jonathan Gilbert @ 21:15

DELETE FROM [tablename] A WHERE A.ROWID > ANY (
  SELECT B.ROWID FROM [tablename] B
  WHERE A.[uniquecolumn] = B.[uniquecolumn]
  [ AND A.[anothercolumn] = B.[anothercolumn] ]
)

Older Posts »

The Silver is the New Black Theme. Bloggen Sie auf WordPress.com.

Folgen

Erhalte jeden neuen Beitrag in deinen Posteingang.