Skip to content

Commit

Permalink
PwCrypt Klasse aus 1.2 für verbesserte Passwortsicherheit eingebunden
Browse files Browse the repository at this point in the history
Kompatibilität zu PHP4 mit Fallback zu alter MD5 Methode
closes #4
  • Loading branch information
Mairu committed Mar 31, 2013
1 parent 755f212 commit a77c140
Show file tree
Hide file tree
Showing 7 changed files with 280 additions and 33 deletions.
8 changes: 4 additions & 4 deletions include/admin/user.php
Expand Up @@ -277,9 +277,9 @@ function updateParent() { parent.setNewAntispam(document.getElementById('tmp').c

if (isset($_POST['passw'])) {
$newPass = genkey (8);
$newPassMD5 = md5($newPass);
$newPassHash = user_pw_crypt($newPass);
icmail ($row->email , 'neues Password' , "Hallo\n\nDein Password wurde soeben von einem Administrator gäendert es ist nun:\n\n$newPass\n\nGruß der Administrator");
db_query('UPDATE `prefix_user` SET pass = "' . $newPassMD5 . '" WHERE id = "' . escape($_POST['uID'], 'integer') . '"');
db_query('UPDATE `prefix_user` SET pass = "' . $newPassHash . '" WHERE id = "' . escape($_POST['uID'], 'integer') . '"');
}
// avatar speichern START
$avatar_sql_update = '';
Expand Down Expand Up @@ -373,9 +373,9 @@ function updateParent() { parent.setNewAntispam(document.getElementById('tmp').c
$msg = 'Der Name ist leider schon vorhanden!';
} else {
$new_pass = $_POST['pass'];
$md5_pass = md5($new_pass);
$passwordHash = user_pw_crypt($new_pass);;
db_query("INSERT INTO prefix_user (name,pass,recht,regist,llogin,email)
VALUES('" . $_POST['name'] . "','" . $md5_pass . "'," . $_POST['recht'] . ",'" . time() . "','" . time() . "','" . $_POST['email'] . "')");
VALUES('" . $_POST['name'] . "','" . $passwordHash . "'," . $_POST['recht'] . ",'" . time() . "','" . time() . "','" . $_POST['email'] . "')");
$userid = db_last_id();
db_query("INSERT INTO prefix_userfields (uid,fid,val) VALUES (" . $userid . ",2,'1')");
db_query("INSERT INTO prefix_userfields (uid,fid,val) VALUES (" . $userid . ",3,'1')");
Expand Down
4 changes: 2 additions & 2 deletions include/contents/user/password_reminder.php
Expand Up @@ -22,11 +22,11 @@
$row = db_fetch_assoc($erg);

$new_pass = genkey(8);
$md5_pass = md5($new_pass);
$passwordHash = user_pw_crypt($new_pass);
$id = md5 (uniqid (rand()));

db_query("INSERT INTO prefix_usercheck (`check`,name,email,pass,datime,ak)
VALUES ('".$id."','".$name."','".$row['email']."','".$md5_pass."',NOW(),2)");
VALUES ('".$id."','".$name."','".$row['email']."','".$passwordHash."',NOW(),2)");

$page = $_SERVER["HTTP_HOST"].$_SERVER["SCRIPT_NAME"];

Expand Down
6 changes: 3 additions & 3 deletions include/contents/user/profil_edit.php
Expand Up @@ -55,10 +55,10 @@
if ( !empty($_POST['np1']) AND !empty($_POST['np2']) AND !empty($_POST['op'])) {
if ($_POST['np1'] == $_POST['np2']) {
$akpw = db_result(db_query("SELECT pass FROM prefix_user WHERE id = ".$_SESSION['authid']),0);
if ($akpw == md5($_POST['op'])) {
$newpw = md5($_POST['np1']);
if (user_pw_check($_POST['op'], $akpw)) {
$newpw = user_pw_crypt($_POST['np1']);
db_query("UPDATE prefix_user SET pass = '".$newpw."' WHERE id = ".$_SESSION['authid']);
setcookie(session_und_cookie_name(), $_SESSION['authid'].'='.$newpw, time() + 31104000, "/" );
user_set_cookie($_SESSION['authid'], $newpw);
$fmsg = $lang['passwortchanged'];
} else {
$fmsg = $lang['passwortwrong'];
Expand Down
214 changes: 214 additions & 0 deletions include/includes/class/pwcrypt.php
@@ -0,0 +1,214 @@
<?php
/**
* @license http://opensource.org/licenses/gpl-2.0.php The GNU General Public License (GPL)
* @copyright (C) 2000-2012 ilch.de
*/
defined('main') or die('no direct access');

/**
* PwCrypt
*
* Achtung: beim Übertragen von mit 2a erzeugten Passwörtern auf einen anderen PC/Server,
* dort kann es u.U. Passieren, dass eine Authentifikation nicht mehr möglich ist,
* da 2a auf einigen System fehlerhafte Ergebnisse liefert.
* Versuche dann bitte 2x bzw. 2y.
*
* @author finke <Surf-finke@gmx.de>
* @copyright Copyright (c) 2012
*/
class PwCrypt
{
const LETTERS = 1; //0001
const NUMBERS = 2; //0010
const ALPHA_NUM = 3; //0011
const URL_CHARACTERS = 4; //0100
const FOR_URL = 7; //0111
const SPECIAL_CHARACTERS = 8; //1000
//Konstanten für die Verschlüsselung
const MD5 = '1';
const BLOWFISH_OLD = '2a';
const BLOWFISH = '2y';
const BLOWFISH_FALSE = '2x';
const SHA256 = '5';
const SHA512 = '6';

private $hashAlgorithm = self::SHA256;

/**
* @param string $lvl Gibt den zu verwendenden Hashalgorithmus an (Klassenkonstante)
*/
public function __construct($lvl = '')
{
if (!empty($lvl)) {
$this->hashAlgorithm = $lvl;
}

/* Wenn 2a gewählt aber 2y verfügbar: nutze trotzdem 2y, da dies sicherer ist; wenn 2x oder 2y gewählt
* aber nicht verfügbar, nutze 2a */
if (version_compare(PHP_VERSION, '5.3.5', '<')
&& ($this->hashAlgorithm === self::BLOWFISH || $this->hashAlgorithm === self::BLOWFISH_FALSE)
) {
$this->hashAlgorithm = self::BLOWFISH_OLD;
} elseif (version_compare(PHP_VERSION, '5.3.5', '>=') && $this->hashAlgorithm == self::BLOWFISH_OLD) {
$this->hashAlgorithm = self::BLOWFISH;
}

// Prüfen welche Hash Funktionen Verfügbar sind. Ab 5.3 werden alle Mitgeliefert
if (version_compare(PHP_VERSION, '5.3.0', '<')) {
if ($this->hashAlgorithm === self::SHA512 && (!defined('CRYPT_SHA512') || CRYPT_SHA512 !== 1)) {
$this->hashAlgoriathm = self::SHA256; // Wenn SHA512 nicht verfügbar, versuche SHA256
}
if ($this->hashAlgorithm === self::SHA256 && (!defined('CRYPT_SHA256') || CRYPT_SHA256 !== 1)) {
$this->hashAlgorithm = self::BLOWFISH_OLD; // Wenn SHA256 nicht verfügbar, versuche BLOWFISH
}
if ($this->hashAlgorithm === self::BLOWFISH_OLD && (!defined('CRYPT_BLOWFISH') || CRYPT_BLOWFISH !== 1)) {
$this->hashAlgorithm = self::MD5; // Wenn BLOWFISH nicht verfügbar, nutze MD5
}
}
}

/**
* Erstellt eine zufällige Zeichenkette
*
* @param integer $size Länge der Zeichenkette
* @param integer $chars Angabe welche Zeichen für die Zeichenkette verwendet werden
* @return string
*/
public static function getRndString($size = 20, $chars = self::LETTERS)
{
if ($chars & self::LETTERS) {
$pool = 'abcdefghijklmnopqrstuvwxyz';
$pool .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
}

if ($chars & self::NUMBERS) {
$pool .='0123456789';
}

//in einer URL nicht reservierte Zeichen
if ($chars & (self::URL_CHARACTERS | self::SPECIAL_CHARACTERS)) {
$pool .= '-_.~';
}

//restiliche Sonderzeichen
if ($chars & self::SPECIAL_CHARACTERS) {
$pool .= '!#$%&()*+,/:;=?@[]';
}

$pool = str_shuffle($pool);
$pool_size = strlen($pool);
$string = '';
for ($i = 0; $i < $size; $i++) {
//TODO: Zufallszahlen aus /dev/random bzw /dev/urandom wenn verfügbar
$string .= $pool[mt_rand(0, $pool_size - 1)];
}
return $string;
}

/**
* Prüft, ob der übergebene Hash, im crpyt Format ist
*
* @param mixed $hash
* @return boolean
*/
public static function isCryptHash($hash)
{
return (preg_match('/^\$([156]|2[axy])\$/', $hash) === 1);
}

/**
* Gibt den Code der gewählten/genutzen Hashmethode zurück (Crpyt Konstante)
*
* @return string
*/
public function getHashAlgorithm()
{
return $this->hashAlgorithm;
}

/**
* Erstellt ein Hash für das übergebene Passwort
*
* @param string $passwd Klartextpasswort
* @param string $salt Salt für den Hashalgorithus
* @param integer $rounds Anzahl der Runden für den verwendeten Hashalgorithmus
* @return string Hash des Passwortes (Ausgabe von crypt())
*/
public function cryptPasswd($passwd, $salt = '', $rounds = 0)
{
$salt_string = '';
switch ($this->hashAlgorithm) {
case self::SHA512:
case self::SHA256:
$salt = (empty($salt) ? self::getRndString(16, self::LETTERS | self::NUMBERS) : $salt);
if ($rounds < 1000 || $rounds > 999999999) {
$rounds = mt_rand(2000, 10000);
}
$salt_string = '$' . $this->hashAlgorithm . '$rounds=' . $rounds . '$' . $salt . '$';
break;
case self::BLOWFISH:
case self::BLOWFISH_OLD:
case self::BLOWFISH_FALSE:
$salt = (empty($salt) ? self::getRndString(22, self::LETTERS | self::NUMBERS) : $salt);
if ($rounds < 4 || $rounds > 31) {
$rounds = mt_rand(6, 10);
}
//Verwendet 2x, wenn verfügbar, auch wenn 2a angegeben wurde
$salt_string = '$' . $this->hashAlgorithm . '$' . $rounds . '$' . $salt . '$';
break;
case self::MD5:
$salt = (empty($salt) ? self::getRndString(12, self::LETTERS | self::NUMBERS) : $salt);
$salt_string = '$' . $this->hashAlgorithm . '$' . $salt . '$';
break;
default:
return false;
}
$crypted_pw = crypt($passwd, $salt_string);
if (strlen($crypted_pw) < 13) {
return false;
}
return $crypted_pw;
}

/**
* Prüft, ob das Klartextpasswort dem Hash "entspricht"
*
* @param mixed $passwd Klartextpasswort
* @param mixed $crypted_passwd Hash des Passwortes (aus der Datenbank)
* @param boolean $backup wenn Check fehlschlägt und das alte passwort mit BLOWFISH_OLD verschlüsselt wurde,
* werden beide Varianten noch einmal explizit geprüft, wenn verfügbar. Nur nach Transfer der Datenbank verwenden,
* da dies ein Sicherheitsrisiko darstellen kann
* @return boolean
*/
public function checkPasswd($passwd, $crypted_passwd, $backup = false)
{
if (empty($crypted_passwd)) {
return false;
}
if (self::isCryptHash($crypted_passwd)) {
$new_chrypt_pw = crypt($passwd, $crypted_passwd);
if (strlen($new_chrypt_pw) < 13) {
return false;
}
} else {
$new_chrypt_pw = md5($passwd);
}
if ($new_chrypt_pw == $crypted_passwd) {
return true;
} else {
if ($backup == true
&& version_compare(PHP_VERSION, '5.3.5', '>=')
&& substr($crypted_passwd, 0, 4) == '$2a$'
) {
$password_x = '$2x$' . substr($crypted_passwd, 4);
$password_y = '$2y$' . substr($crypted_passwd, 4);
$password_neu_x = crypt($passwd, $password_x);
$password_neu_y = crypt($passwd, $password_y);
if ($password_neu_x === $password_x || $password_neu_y === $password_y) {
return true;
}
}
}
return false;
}
}
44 changes: 37 additions & 7 deletions include/includes/func/user.php
Expand Up @@ -74,6 +74,36 @@ function session_und_cookie_name () {
return (md5(dirname($_SERVER["HTTP_HOST"].$_SERVER["SCRIPT_NAME"]).DBPREF));
}

function user_pw_crypt($plainPassword) {
if (version_compare(PHP_VERSION, '5.0') !== -1) {
$pwCrypt = new PwCrypt();
return $pwCrypt->cryptPasswd($plainPassword);
}
return md5($plainPassword);
}

function user_pw_check($plainPassword, &$passwordHash, $userId = false) {
if (version_compare(PHP_VERSION, '5.0') !== -1) {
$pwCrypt = new PwCrypt();
$correct = $pwCrypt->checkPasswd($plainPassword, $passwordHash);
if ($correct && $userId !== false && !PwCrypt::isCryptHash($passwordHash)) {
$passwordHash = $pwCrypt->cryptPasswd($plainPassword);
db_query('UPDATE `prefix_user` SET `pass` = "' . $passwordHash . '" WHERE `id` = ' . $userId);
}
return $correct;
}
return md5($plainPassword) === $passwordHash;
}

function user_set_cookie($id, $cryptedPassword) {
$cookieString = $id . '=' . md5(DBUSER . $cryptedPassword);
setcookie($_SESSION['authsess'], $cookieString , strtotime('+1 year'), '/' );
}

function user_cookie_check($cookieHash, $cryptedPassword) {
return md5(DBUSER . $cryptedPassword) == $cookieHash;
}

function user_login_check () {
if ( isset ($_POST['user_login_sub']) AND isset ($_POST['name']) AND isset ($_POST['pass']) ) {
debug ('posts vorhanden');
Expand All @@ -85,15 +115,15 @@ function user_login_check () {
if ( db_num_rows($erg) == 1 ) {
debug ('user gefunden');
$row = db_fetch_assoc($erg);
if ( $row['pass'] == md5($_POST['pass']) ) {
if (user_pw_check($_POST['pass'], $row['pass'], $row['id']) ) {
debug ('passwort stimmt ... '.$row['name']);
$_SESSION['authname'] = $row['name'];
$_SESSION['authid'] = $row['id'];
$_SESSION['authright'] = $row['recht'];
$_SESSION['lastlogin'] = $row['llogin'];
$_SESSION['authsess'] = session_und_cookie_name();
db_query("UPDATE prefix_online SET uid = ".$_SESSION['authid']." WHERE sid = '".session_id()."'");
setcookie($_SESSION['authsess'], $row['id'].'='.$row['pass'] , time() + 31104000, "/" );
user_set_cookie($row['id'], $row['pass']);
user_set_grps_and_modules();
return (true);
}
Expand All @@ -117,7 +147,7 @@ function user_auto_login_check () {
if (db_num_rows($erg) == 1) {
debug ('benutzer gefunden');
$row = db_fetch_assoc($erg);
if ($row['pass'] == $pw) {
if (user_cookie_check($pw, $row['pass'])) {
debug ('passwoerter stimmen');
debug ($row['name']);
$_SESSION['authname'] = $row['name'];
Expand All @@ -126,7 +156,7 @@ function user_auto_login_check () {
$_SESSION['lastlogin'] = $row['llogin'];
$_SESSION['authsess'] = $cn;
db_query("UPDATE prefix_online SET uid = ".$_SESSION['authid']." WHERE sid = '".session_id()."'");
setcookie($cn, $row['id'].'='.$row['pass'], time() + 31104000, "/" );
user_set_cookie($row['id'], $row['pass']);
return (true);
}
}
Expand Down Expand Up @@ -287,7 +317,7 @@ function user_regist ($name, $mail, $pass) {
$new_pass = $pass;
}

$md5_pass = md5($new_pass);
$passwordHash = user_pw_crypt($new_pass);
$confirmlinktext = '';

# confirm insert in confirm tb not confirm insert in user tb
Expand All @@ -297,10 +327,10 @@ function user_regist ($name, $mail, $pass) {
$id = md5 (uniqid (rand()));
$confirmlinktext = "\n".$lang['registconfirm']."\n\n".sprintf($lang['registconfirmlink'], $page, $id );
db_query("INSERT INTO prefix_usercheck (`check`,name,email,pass,datime,ak)
VALUES ('".$id."','".$name."','".$mail."','".$md5_pass."',NOW(),1)");
VALUES ('".$id."','".$name."','".$mail."','".$passwordHash."',NOW(),1)");
} else {
db_query("INSERT INTO prefix_user (name,pass,recht,regist,llogin,email,status,opt_mail,opt_pm)
VALUES('".$name."','".$md5_pass."',-1,'".time()."','".time()."','".$mail."',1,1,1)");
VALUES('".$name."','".$passwordHash."',-1,'".time()."','".time()."','".$mail."',1,1,1)");
$userid = db_last_id();
}
$regmail = sprintf($lang['registemail'],$name, $confirmlinktext, $name, $new_pass);
Expand Down
1 change: 1 addition & 0 deletions include/includes/loader.php
Expand Up @@ -11,6 +11,7 @@
require_once('include/includes/class/tpl.php');
require_once('include/includes/class/design.php');
require_once('include/includes/class/menu.php');
require_once('include/includes/class/pwcrypt.php');

# fremde classes laden
if (version_compare(PHP_VERSION, '5.3') == -1) {
Expand Down

0 comments on commit a77c140

Please sign in to comment.