From 3eecd852575dfcca95609f7f2b4b4ca9e3f85f20 Mon Sep 17 00:00:00 2001 From: Daniel Triendl Date: Wed, 5 Jun 2013 01:11:26 +0200 Subject: [PATCH 1/4] bcrypt --- index.php | 1 + user.php | 2 +- weave_hash.php | 191 ++++++++++++++++++++++++++++++++++++++++++++++ weave_storage.php | 29 ++++--- weave_utils.php | 11 ++- 5 files changed, 216 insertions(+), 18 deletions(-) create mode 100644 weave_hash.php diff --git a/index.php b/index.php index 1852a25..e59d393 100644 --- a/index.php +++ b/index.php @@ -54,6 +54,7 @@ require_once 'weave_storage.php'; require_once 'weave_basic_object.php'; require_once 'weave_utils.php'; + require_once 'weave_hash.php'; require_once "WBOJsonOutput.php"; //header("Content-type: application/json"); diff --git a/user.php b/user.php index 6f3e004..4c8e905 100644 --- a/user.php +++ b/user.php @@ -244,7 +244,7 @@ log_error("user.php: POST password "); //to do // change pw in db - if($db->change_password($username, $new_pwd)) + if($db->change_password($new_pwd)) exit("success"); else report_problem(WEAVE_ERROR_INVALID_PROTOCOL, 503); //server db messed up somehow diff --git a/weave_hash.php b/weave_hash.php new file mode 100644 index 0000000..3778e9b --- /dev/null +++ b/weave_hash.php @@ -0,0 +1,191 @@ + +# +# Alternatively, the contents of this file may be used under the terms of +# either the GNU General Public License Version 2 or later (the "GPL"), or +# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +interface WeaveHash { + public function hash($input); + public function verify($input, $existingHash); + public function needsUpdate($existingHash); +} + +class WeaveHashMD5 implements WeaveHash { + public function hash($input) { + return md5($input); + } + + public function verify($input, $existingHash) { + return $this->hash($input) == $existingHash; + } + + public function needsUpdate($existingHash) { + return substr($existingHash, 0, 4) == "$2a$"; + } +} + +class WeaveHashBCrypt implements WeaveHash { + private $_rounds; + + public function __construct($rounds = 12) { + if(CRYPT_BLOWFISH != 1) { + throw new Exception("bcrypt not available"); + } + + $this->_rounds = $rounds; + } + + public function hash($input) { + $hash = crypt($input, $this->getSalt()); + + if (strlen($hash) <= 13) { + throw new Exception("error while generating hash"); + } + + return $hash; + } + + public function verify($input, $existingHash) { + if ($this->isMD5($existingHash)) { + $md5 = new WeaveHashMD5(); + return $md5->verify($input, $existingHash); + } + + $hash = crypt($input, $existingHash); + + return $hash === $existingHash; + } + + public function needsUpdate($existingHash) { + $identifier = $this->getIdentifier(); + return substr($existingHash, 0, strlen($identifier)) != $identifier; + } + + private function isMD5($existingHash) { + return substr($existingHash, 0, 4) != "$2a$"; + } + + private function getSalt() { + $salt = $this->getIdentifier(); + + $bytes = $this->getRandomBytes(16); + + $salt .= $this->encodeBytes($bytes); + + return $salt; + } + + private function getIdentifier() { + return sprintf("$2a$%02d$", $this->_rounds); + } + + private $randomState; + private function getRandomBytes($count) { + $bytes = ''; + + if(function_exists('openssl_random_pseudo_bytes') && + (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN')) { // OpenSSL slow on Win + $bytes = openssl_random_pseudo_bytes($count); + } + + if($bytes === '' && is_readable('/dev/urandom') && + ($hRand = @fopen('/dev/urandom', 'rb')) !== FALSE) { + $bytes = fread($hRand, $count); + fclose($hRand); + } + + if(strlen($bytes) < $count) { + $bytes = ''; + + if($this->randomState === null) { + $this->randomState = microtime(); + if(function_exists('getmypid')) { + $this->randomState .= getmypid(); + } + } + + for($i = 0; $i < $count; $i += 16) { + $this->randomState = md5(microtime() . $this->randomState); + + if (PHP_VERSION >= '5') { + $bytes .= md5($this->randomState, true); + } else { + $bytes .= pack('H*', md5($this->randomState)); + } + } + + $bytes = substr($bytes, 0, $count); + } + + return $bytes; + } + + private function encodeBytes($input) { + // The following is code from the PHP Password Hashing Framework + $itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + + $output = ''; + $i = 0; + do { + $c1 = ord($input[$i++]); + $output .= $itoa64[$c1 >> 2]; + $c1 = ($c1 & 0x03) << 4; + if ($i >= 16) { + $output .= $itoa64[$c1]; + break; + } + + $c2 = ord($input[$i++]); + $c1 |= $c2 >> 4; + $output .= $itoa64[$c1]; + $c1 = ($c2 & 0x0f) << 2; + + $c2 = ord($input[$i++]); + $c1 |= $c2 >> 6; + $output .= $itoa64[$c1]; + $output .= $itoa64[$c2 & 0x3f]; + } while (1); + + return $output; + } +} + +class WeaveHashFactory { + public static function factory() { + if (BCRYPT) { + return new WeaveHashBCrypt(BCRYPT_ROUNDS); + } else { + return new WeaveHashMD5(); + } + } +} + +?> \ No newline at end of file diff --git a/weave_storage.php b/weave_storage.php index f08d7dd..5be3f6b 100644 --- a/weave_storage.php +++ b/weave_storage.php @@ -22,6 +22,7 @@ # # Contributor(s): # Toby Elliott (telliott@mozilla.com) +# Daniel Triendl # # Alternatively, the contents of this file may be used under the terms of # either the GNU General Public License Version 2 or later (the "GPL"), or @@ -725,7 +726,8 @@ class WeaveStorage $create_statement = "insert into users values (:username, :md5)"; $sth = $this->_dbh->prepare($create_statement); - $password = md5($password); + $hash = WeaveHashFactory::factory(); + $password = $hash->hash($password); $sth->bindParam(':username', $username); $sth->bindParam(':md5', $password); $sth->execute(); @@ -739,16 +741,15 @@ class WeaveStorage return 1; } - function change_password($username, $password) + function change_password($hash) { try { $update_statement = "update users set md5 = :md5 where username = :username"; $sth = $this->_dbh->prepare($update_statement); - $password = md5($password); - $sth->bindParam(':username', $username); - $sth->bindParam(':md5', $password); + $sth->bindParam(':username', $this->_username); + $sth->bindParam(':md5', $hash); $sth->execute(); } catch( PDOException $exception ) @@ -784,31 +785,27 @@ class WeaveStorage } - function authenticate_user($password) + function get_password_hash() { log_error("auth-user: " . $this->_username); try { - $select_stmt = 'select username from users where username = :username and md5 = :md5'; + $select_stmt = 'select md5 from users where username = :username'; $sth = $this->_dbh->prepare($select_stmt); $username = $this->_username; - $password = md5($password); $sth->bindParam(':username', $username); - $sth->bindParam(':md5', $password); $sth->execute(); } catch( PDOException $exception ) { - error_log("authenticate_user: " . $exception->getMessage()); + error_log("get_password_hash: " . $exception->getMessage()); throw new Exception("Database unavailable", 503); } - if (!$result = $sth->fetch(PDO::FETCH_ASSOC)) - { - return null; - } - - return 1; + $result = $sth->fetchColumn(); + if ($result === FALSE) $result = ""; + + return $result; } } diff --git a/weave_utils.php b/weave_utils.php index cbe1afc..a2b1047 100644 --- a/weave_utils.php +++ b/weave_utils.php @@ -19,6 +19,7 @@ # the Initial Developer. All Rights Reserved. # # Contributor(s): +# Daniel Triendl # # Alternatively, the contents of this file may be used under the terms of # either the GNU General Public License Version 2 or later (the "GPL"), or @@ -212,13 +213,21 @@ try { - if ( ! $db->authenticate_user(fix_utf8_encoding($auth_pw)) ) + $existingHash = $db->get_password_hash(); + $hash = WeaveHashFactory::factory(); + + if ( ! $hash->verify(fix_utf8_encoding($auth_pw), $existingHash) ) { log_error("Auth failed 2 {"); log_error(" User pw: ". $auth_user ."|".$auth_pw ."|md5:". md5($auth_pw) ."|fix:". fix_utf8_encoding($auth_pw) ."|fix md5 ". md5(fix_utf8_encoding($auth_pw))); log_error(" Url_user: ".$url_user); + log_error(" Existing hash: ".$existingHash); log_error("}"); report_problem('Authentication failed', '401'); + } else { + if ( $hash->needsUpdate($existingHash) ) { + $db->change_password($hash->hash(fix_utf8_encoding($auth_pw))); + } } } catch(Exception $e) From 0030dd14be23fbfb76389a36387c909922209202 Mon Sep 17 00:00:00 2001 From: Daniel Triendl Date: Wed, 5 Jun 2013 01:11:41 +0200 Subject: [PATCH 2/4] bcrypt testcase --- test/hash.php | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 test/hash.php diff --git a/test/hash.php b/test/hash.php new file mode 100644 index 0000000..a7652b3 --- /dev/null +++ b/test/hash.php @@ -0,0 +1,59 @@ +hash($pwd) . "\n"; + $time = microtime(true) - $time_start; + echo "Hashing took " . $time . " seconds\n"; + + if (!$hash->verify($pwd, '$2a$12$O2Bn6lDUYS5NDIJ1uCZjGezSI/jeGTD7Ow0bd3PFMRBcGIqfqI4Oi')) { + throw new Exception("bcrypt hash compare failed"); + } + + if (!$hash->needsUpdate(md5($pwd))) { + throw new Exception("bcrypt hash needs update."); + } + + if ($hash->needsUpdate('$2a$12$O2Bn6lDUYS5NDIJ1uCZjGezSI/jeGTD7Ow0bd3PFMRBcGIqfqI4Oi')) { + throw new Exception("bcrypt hash doesn't needs update."); + } + + if (!$hash->verify($pwd, 'a96b71c678b01b98b9f7a0d8ec4b633b')) { + throw new Exception("bcrypt hash compare with md5 failed"); + } + + $hash2 = new WeaveHashBCrypt(6); + + if (!$hash2->needsUpdate('$2a$12$O2Bn6lDUYS5NDIJ1uCZjGezSI/jeGTD7Ow0bd3PFMRBcGIqfqI4Oi')) { + throw new Exception("bcrypt hash needs update because of different rounds."); + } + + $hashmd5 = new WeaveHashMD5(); + if (!$hashmd5->verify($pwd, 'a96b71c678b01b98b9f7a0d8ec4b633b')) { + throw new Exception("md5 hash compare failed"); + } + + if (!$hashmd5->needsUpdate('$2a$12$O2Bn6lDUYS5NDIJ1uCZjGezSI/jeGTD7Ow0bd3PFMRBcGIqfqI4Oi')) { + throw new Exception("md5 hash needs update."); + } + + if ($hashmd5->needsUpdate(md5($pwd))) { + throw new Exception("md5 hash doesn't need update."); + } + + echo "all tests ok\n"; + exit(0); +} catch(Exception $e) { + echo $e->getMessage() . "\n"; + exit(1); +} + +?> \ No newline at end of file From 649fa92bfad50402884f0d52aeab53624747c406 Mon Sep 17 00:00:00 2001 From: Daniel Triendl Date: Wed, 5 Jun 2013 02:08:17 +0200 Subject: [PATCH 3/4] Check if BCRYPT const is defined --- weave_hash.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weave_hash.php b/weave_hash.php index 3778e9b..fc951a5 100644 --- a/weave_hash.php +++ b/weave_hash.php @@ -180,7 +180,7 @@ class WeaveHashBCrypt implements WeaveHash { class WeaveHashFactory { public static function factory() { - if (BCRYPT) { + if (defined("BCRYPT") && BCRYPT) { return new WeaveHashBCrypt(BCRYPT_ROUNDS); } else { return new WeaveHashMD5(); From c6f4f1a8011ae1c0e13ad349bbe66f1fbbcaeedf Mon Sep 17 00:00:00 2001 From: Daniel Triendl Date: Wed, 5 Jun 2013 02:17:13 +0200 Subject: [PATCH 4/4] add bcrypt to settings.php on setup --- setup.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/setup.php b/setup.php index 349ca01..4df213f 100644 --- a/setup.php +++ b/setup.php @@ -21,6 +21,7 @@ # the Initial Developer. All Rights Reserved. # # Contributor(s): +# Daniel Triendl # # Alternatively, the contents of this file may be used under the terms of # either the GNU General Public License Version 2 or later (the "GPL"), or @@ -149,6 +150,10 @@ function write_config_file($dbt, $dbh, $dbn, $dbu, $dbp, $fsRoot) { $cfg_content .= " define(\"MYSQL_USER\", \"$dbu\");\n"; $cfg_content .= " define(\"MYSQL_PASSWORD\", \"$dbp\");\n"; } + $cfg_content .= "\n"; + $cfg_content .= " // Use bcrypt instead of MD5 for password hashing\n"; + $cfg_content .= " define(\"BCRYPT\", true);\n"; + $cfg_content .= " define(\"BCRYPT_ROUNDS\", 12);\n"; $cfg_content .= "\n?>\n";