Jörg Reinholz: Kleine Bibliothek für Passwort-Hash-Erzeugug und Verifizierung

In der Hoffnung, dass es gefällt stelle ich das hier mal online. Könnte für jene nützlich sein, die sich mit alten PHP-Versionen herumschlagen müssen oder nicht wissen, für welche sie programmieren.

Zum Testen in Zeile 2 den Funktionsaufruf test_me(); "scharfschalten" und die Funktion test_hash_passwd() durch die Kommentare modifizieren.

<?php  
#test_me();  
error_reporting(0);  
## Erzeugt gesalzte Passwörter nach "bester" Möglichkeit. Reihenfolge mit  
## 	* password_hash  
## 	* CRYPT_SHA512  
## 	* CRYPT_SHA256  
## 	* CRYPT_BLOWFISH  
## 	* Apache MD5 - $apr1$ (als Fallback)  
## und kann diese verifizieren  
## verifiziert zusätzlich auch Passwörter, deren Hash früher mal mit  
## 	* htpasswd -nbs myName myPassword (SHA1, {sha})  
##      * gesalzenem md5 ( siehe test_hash_passwd() )  
##      * gesalzenem sha1 ( siehe test_hash_passwd() )  
## erzeugt wurde...  
  
function create_salt($l=16, $allowed='1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJLKMNOPQRSTUVWXYZ./') {  
	$salt='';  
	for ( $i = 0; $i < $l; $i++ ) {  
		$salt .= $allowed{rand( 0, strlen($allowed) - 1 )};  
	}  
return $salt;  
}  
  
  
function fastix_hash_passwd ($string) {  
	if ( function_exists ('password_hash') ) {  
		return password_hash($string, PASSWORD_DEFAULT);  
	} else {  
		if ( function_exists ('crypt') and defined('CRYPT_SHA512') and  CRYPT_SHA512 ) {  
			return crypt( $string, '$6$rounds=5000$' .  create_salt() );  
		} else if ( function_exists ('crypt') and defined('CRYPT_SHA256') and  CRYPT_SHA256 ) {  
			return crypt( $string, '$5$rounds=5000$' .  create_salt() );	  
		} else if ( function_exists ('crypt') and defined('CRYPT_BLOWFISH') and CRYPT_BLOWFISH ) {  
			if ( version_compare(phpversion(), '5.3.7', '>') ) {  
			      return crypt( $string, '$2y$12$' . create_salt(22) );	  
			} else {  
			      return crypt( $string, '$2a$12$' . create_salt(22) );  
			}  
		} else {  
			# Fallback: Apache md5 - $apr1$  
			$salt = create_salt(8, 'abcdefghijklmnopqrstuvwxyz0123456789');  
			# Code from http://php.net/manual/de/function.crypt.php, comment No. 8:  
			$len = strlen($string);  
			$tmp='';  
			$text = $string.'$apr1$'.$salt;  
			$bin = pack("H32", md5($string.$salt.$string));  
			for($i = $len; $i > 0; $i -= 16) { $text .= substr($bin, 0, min(16, $i)); }  
			for($i = $len; $i > 0; $i >>= 1) { $text .= ($i & 1) ? chr(0) : $string{0}; }  
			$bin = pack("H32", md5($text));  
			for($i = 0; $i < 1000; $i++) {  
			    $new = ($i & 1) ? $string : $bin;  
			    if ($i % 3) $new .= $salt;  
			    if ($i % 7) $new .= $string;  
			    $new .= ($i & 1) ? $bin : $string;  
			    $bin = pack("H32", md5($new));  
			}  
			for ($i = 0; $i < 5; $i++) {  
			    $k = $i + 6;  
			    $j = $i + 12;  
			    if ($j == 16) $j = 5;  
			    $tmp = $bin[$i].$bin[$k].$bin[$j].$tmp;  
			}  
			$tmp = chr(0).chr(0).$bin[11].$tmp;  
			$tmp = strtr(strrev(substr(base64_encode($tmp), 2)), "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");  
			return "$"."apr1"."$".$salt."$".$tmp;  
		}  
	}  
	header('HTTP/1.1 500 Script Generated Error (NO HASH-METHOD Found)');  
	exit;  
}  
  
function fastix_check_passwd ($string, $string_hashed, $error_exit=false) {  
  
	# htpasswd -nbm myName myPassword  
	if ( '$apr1$' == substr($string_hashed, 0, 6) ) {  
		list ($dummy, $method, $salt, $rest)=explode('$', $string_hashed, 4);  
		# Code From http://php.net/manual/de/function.crypt.php, comment No. 8:  
	        $len = strlen($string);  
	        $tmp='';  
		$text = $string.'$apr1$'.$salt;  
		$bin = pack("H32", md5($string.$salt.$string));  
		for($i = $len; $i > 0; $i -= 16) { $text .= substr($bin, 0, min(16, $i)); }  
		for($i = $len; $i > 0; $i >>= 1) { $text .= ($i & 1) ? chr(0) : $string{0}; }  
		$bin = pack("H32", md5($text));  
		for($i = 0; $i < 1000; $i++) {  
		    $new = ($i & 1) ? $string : $bin;  
		    if ($i % 3) $new .= $salt;  
		    if ($i % 7) $new .= $string;  
		    $new .= ($i & 1) ? $bin : $string;  
		    $bin = pack("H32", md5($new));  
		}  
		for ($i = 0; $i < 5; $i++) {  
		    $k = $i + 6;  
		    $j = $i + 12;  
		    if ($j == 16) $j = 5;  
		    $tmp = $bin[$i].$bin[$k].$bin[$j].$tmp;  
		}  
		$tmp = chr(0).chr(0).$bin[11].$tmp;  
		$tmp = strtr(strrev(substr(base64_encode($tmp), 2)), "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");  
		#$hashed = "$"."apr1"."$".$salt."$".$tmp;  
		if ( $rest == $tmp) { return 'bestaetigt:apache2-apr1'; }  
	}  
	  
	# htpasswd -nbs myName myPassword  
	if ( '{SHA}' == substr($string_hashed, 0, 5) ) {  
		$hash='{SHA}' . base64_encode(sha1($string, TRUE));  
		if ( $hash == $string_hashed) { return 'bestaetigt:apache2-sha1'; }  
	}  
  
	if ( function_exists ('password_verify') ) {  
		if ( password_verify($string, $string_hashed) ) { return 'bestaetigt:password_verify()'; }  
	}  
	if ( function_exists ('crypt') ) {  
		list( $dummy, $method, $rest ) = explode( '$', $string_hashed, 3 );	  
		if ( '2y' == $method or '2a' == $method ) {  
		      list ($dummy, $method, $rounds, $rest)=explode('$', $string_hashed, 4);  
		      $salt = substr($rest, 0, 22);  
		      if ( $string_hashed == crypt($string, '$' . $method . '$' . $rounds . '$' . $salt) ) { return 'bestaetigt:crypt():'.$method; }  
		}  
		if ( '6' == $method  or '5' == $method or '1' == $method ) {  
		      list ($dummy, $method, $rounds, $salt, $rest) = explode('$', $string_hashed, 5);  
		      if ( $string_hashed == crypt($string, '$' . $method . '$' . $rounds . '$' . $salt) ) { return 'bestaetigt:crypt()'.$method; }  
		}		  
	}  
	if ( '1' == $method ) {  
		trigger_error("Warnung: Das Passwort ist nicht ausreichend sicher gehasht.", E_USER_NOTICE);	  
		list( $dummy, $method, $rounds, $salt, $rest )=explode( '$', $string_hashed, 5 );  
		$rounds = str_replace('rounds=', '', $rounds);  
		for ( $i = 0; $i < $rounds; $i++ ) {  
			$string = md5($salt.$string);  
		}  
		$string = '$1$rounds=' . $rounds . '$' . $salt . '$' . $string;  
		if ( $string_hashed == $string )  { return 'bestaetigt:md5()'; }  
	}  
	if ( 'sha1' == $method ) {  
		trigger_error("Warnung: Das Passwort ist nicht ausreichend sicher gehasht.", E_USER_NOTICE);	  
		list ($dummy, $method, $rounds, $salt, $rest)=explode('$', $string_hashed, 5);  
		$rounds = str_replace( 'rounds=', '', $rounds );  
		for ( $i = 0; $i < $rounds; $i++ ) {  
			$string = sha1($salt.$string);  
		}  
		$string = '$sha1$rounds=' . $rounds . '$' . $salt . '$' . $string;  
		if ( $string_hashed == $string )  { return 'bestaetigt:sha1()'; }  
	}  
	if ( $error_exit ) { trigger_error("Unbekannte Methode oder falsches Passwort.", $error_exit); }  
	return false;  
}  
  
### Tests ####  
  
function test_hash_passwd ($string) {  
#	return password_hash($string, PASSWORD_DEFAULT);  
#	return crypt( $string, '$6$rounds=5000$' .  create_salt() );  
#	return crypt( $string, '$5$rounds=5000$' .  create_salt() );	  
	return crypt( $string, '$2y$12$' . create_salt(22) );	  
	return crypt( $string, '$2a$12$' . create_salt(22) );  
	$salt=create_salt(32); for ( $i=0; $i<50000; $i++ ) { $string=sha1($salt.$string); } return '$sha1$rounds=50000$' . $salt . '$' . $string;  
	$salt=create_salt(32); for ( $i=0; $i<50000; $i++ ) { $string=md5($salt.$string); } return '$1$rounds=50000$' . $salt . '$' . $string;  
}  
  
function test_me() {  
error_reporting(E_ALL);  
	$string='Dieses Passwort ist vollkommen richtig';  
	$string_hashed=test_hash_passwd ($string);  
	echo "'$string'\n", "'$string_hashed'\n", fastix_check_passwd($string,  $string_hashed, E_USER_ERROR), "\n";  
	$string='Dieses Passwort ist nur fast richtig:2'; echo "Gegentest: '$string'\n", fastix_check_passwd($string, $string_hashed, E_USER_NOTICE), "\n";  
	print fastix_check_passwd('myPassword', '{SHA}VBPuJHI7uixaa6LQGWx4s+5GKNE=')."\n";  
	print fastix_check_passwd('myPassword', '$apr1$r31.....$HqJZimcKQFAMYayBlzkrA/')."\n";  
}  

  1. Hakuna matata!

    function create_salt($l=16, $allowed='1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJLKMNOPQRSTUVWXYZ./') {

    $salt='';
    for ( $i = 0; $i < $l; $i++ ) {
    $salt .= $allowed{rand( 0, strlen($allowed) - 1 )};
    }
    return $salt;
    }

      
    [rand()](http://php.net/manual/de/function.rand.php) liefert keinen kryptografisch sicheren Zufallswert. [openssl_random_pseudo_bytes()](http://php.net/manual/de/function.openssl-random-pseudo-bytes.php) wäre hier wohl angemessen.  
    
    -- 
    “All right, then, I'll go to hell.” – Huck Finn
    
    1. Lieber 1UnitedPower,

      openssl_random_pseudo_bytes() wäre hier wohl angemessen.

      diese Funktion steht aber erst ab PHP 5 >= 5.3.0 zur Verfügung und fastix hatte explizit geschrieben, dass seine Funktionen für ältere PHP-Versionen geschrieben sind!

      Liebe Grüße,

      Felix Riesterer.

      --
      "Wäre die EU ein Staat, der die Aufnahme in die EU beantragen würde, müsste der Antrag zurückgewiesen werden - aus Mangel an demokratischer Substanz." (Martin Schulz, Präsident des EU-Parlamentes)
      1. Hakuna matata!

        Lieber 1UnitedPower,

        openssl_random_pseudo_bytes() wäre hier wohl angemessen.

        diese Funktion steht aber erst ab PHP 5 >= 5.3.0 zur Verfügung und fastix hatte explizit geschrieben, dass seine Funktionen für ältere PHP-Versionen geschrieben sind!

        Die diversen Hash-Algorithmen, die Jörg der Reihe nach abtastet, stehen auch erst ab PHP 5.3 zur Verfügung, lediglich der md5-Fallback als letzte Möglichkeit funktioniert schon mit PHP4.

        Für die Salt-Generierung könnte man eine ähnliche Kaskadierung vornehmen. Man könnte zum Beispiel erst auf openssl_random_pseudo_bytes() und anschließend auf mcrypt_create_iv() (ab PHP4) testen. Wenn man tatsächlich noch auf rand() oder mt_rand() zurückfallen möchte, dann sollte man den Entwickler zumindest warnen, dass das generierte Salt unsicher ist.

        --
        “All right, then, I'll go to hell.” – Huck Finn
      2. Moin!

        Naja. Das Vorhanden sein von openssl_random_pseudo_bytes() könnte man mit function_exists('openssl_random_pseudo_bytes') prüfen. Ich schlage dennoch nach den Hinweisen von United Power und Felix als Kompromiss vor, mt_rand() zu nutzen.

        Grund: openssl_random_pseudo_bytes() liefert nämlich überwiegend Bytes welche nicht in dem für diverse Salts gültigem Bereich liegen (insbesondere auch: nicht druckbare). Von 253 möglichen Bytes (ich nehme mangels Dokumentation an, EOT, EOF und NUL werden aus gutem Grund nicht geliefert) sind (je nach Hash-Verfahren) nur 36, 62 oder 64 in Salts gültig. Die anderen müsste man aussortieren. Klar: auch das ist machbar. Nur holt man dann im Durchschnitt (bei 64 gültigen Zeichen) ungefähr 88 Bytes ab um einen 22-Zeichen starken, gültigen Salt zu haben. Das klingt "teuer".

        Weitere Erläuterungen (welche meine "Vorredner" inhaltlich gewiss schon kennen): Bei den Salts kommt es nicht so sehr auf optimalen Zufall an wie bei richtiger Kryptographie. Das Salzen und Hashen in mehreren Runden soll bewirken, dass ein Angreifer - der die Nutzernamen und die Hashes bereits hat (also Rundenzahl und Salts sogar kennt) - die Passwörter nachfolgend nicht einfach mit Rainbow-Tables oder aus einer schlichten Datenbank ermitteln und für den Angriff auf weitere Systeme (das, woher er die Daten hat, ist oft bereits vollständig "erlegt") nutzen kann. Das Verfahren soll bei vertretbaren Aufwand für die "Verteidiger" die Datenmenge, welche ein "Angreifer" speichern und verarbeiten müsste, schlicht ganz gewaltig aufblähen.

        Das Verfahren soll also nur den Aufwand dafür so weit nach oben treiben, dass dieses (wie es auch in der Kryptographie stets gilt: derzeit) nicht in vernünftiger Zeit möglich ist. Vermutlich dürften die Mängel von rand() oder mt_rand() nicht merklich greifen, weil einfach die Zahl der zu hashenden Passwörter zu gering ist.

        Jörg Reinholz

        1. Hakuna matata!

          Naja. Das Vorhanden sein von openssl_random_pseudo_bytes() könnte man mit function_exists('openssl_random_pseudo_bytes') prüfen. Ich schlage dennoch nach den Hinweisen von United Power und Felix als Kompromiss vor, mt_rand() zu nutzen.

          Bei mt_rand() soll nur die Verteilung und die Geschwindigkeit besser sein als beim gewöhnlichen rand(). Es erfüllt trotzdem nicht die Ansprüche eines kryptografischen Zufalls, diese Eigenschaften enthält man in der Regel nur dann, wenn die Zufallsdaten von einer nicht deterministischen Entropiequelle gespeist werden.

          Grund: openssl_random_pseudo_bytes() liefert nämlich überwiegend Bytes welche nicht in dem für diverse Salts gültigem Bereich liegen (insbesondere auch: nicht druckbare).

          Das ist nur eine Frage der richtigen Kodierung, da bieten sich diverse Funktionen an, zum Beispiel base64_encode oder bin2hex, jenachdem womit der Hash-Algorithmus umgehen kann.

          Von 253 möglichen Bytes (ich nehme mangels Dokumentation an, EOT, EOF und NUL werden aus gutem Grund nicht geliefert) sind (je nach Hash-Verfahren) nur 36, 62 oder 64 in Salts gültig. Die anderen müsste man aussortieren.

          Nein nicht aussortieren, sondern so umkodieren, dass sie Element der Zielmenge werden.

          Klar: auch das ist machbar. Nur holt man dann im Durchschnitt (bei 64 gültigen Zeichen) ungefähr 88 Bytes ab um einen 22-Zeichen starken, gültigen Salt zu haben. Das klingt "teuer".

          Beim Kodierung geht nämlich auch keine Entropie verloren. Und dieses Problem stellt sich nicht.

          --
          “All right, then, I'll go to hell.” – Huck Finn
          1. Bei mt_rand() soll nur die Verteilung und die Geschwindigkeit besser sein als beim gewöhnlichen rand(). Es erfüllt trotzdem nicht die Ansprüche eines kryptografischen Zufalls, diese Eigenschaften enthält man in der Regel nur dann, wenn die Zufallsdaten von einer nicht deterministischen Entropiequelle gespeist werden.

            Das ist hier aber nicht notwendig. Es ist nur notwendig, möglichst viele _verschiedene_ Salts zu haben. Es gilt auch nicht als Problem, dass mit dem Hash auch der Salt bekannt wird. Demnach kann es auch nicht ein Problem sein, wenn man - mit immer noch ziemlich geringer Wahrscheinlichkeit des Treffers - spekulieren kann, welcher Salt wohl zu jeden vollen 10, 20, 30, 40 oder 50 Sekunden nach Rechnerstart generiert wird.

            Wenn ein Angreifer jetzt hingänge und "abermilliarden" gehaschte Passwörter von abertausenden Quellen zusammentragen würde, dann könnte für diesen womöglich ein Vorteil entstehen. Der zerfällt aber gleich wieder, weil diese vielen verschiedenen Quellen natürlich andere Ausgangsdaten für deterministischen Entropiequellen haben. Auf deutsch gesagt: Es nützt dem Angreifer nichts. Jedenfalls nicht so lange rand() oder mt_rand RICHTIG schlecht wären so dass Salts gehäuft identisch sind. Das findet aber nicht statt.

            Ich habe das mal getestet:

            <?php  
            $bytes=1024 * 1024 * 1024;  
            print "Generiere $bytes Bytes große Dateien.\n";  
              
            file_put_contents('/tmp/open_ssl_test.bin', openssl_random_pseudo_bytes($bytes));  
              
            $bin=''; $i=0;  
            while ($i < $bytes) {  
                $bin .= chr(rand(0,255));  
                $i++;  
            }  
            file_put_contents('/tmp/test.bin',$bin);  
              
            $bin=''; $i=0;  
            while ($i < $bytes) {  
                $bin .= chr(mt_rand(0,255));  
                $i++;  
            }  
            file_put_contents('/tmp/mt_test.bin',$bin);
            

            Mit diesem Skript habe ich die Dateien erzeugt. Die sind jeweils 1 GigaByte groß:

            fastix@trainer:/tmp$ php test.php
            Generiere 1073741824 Bytes große Dateien.
            fastix@trainer:/tmp$ ll *test.bin*
            -rw-rw-r-- 1 fastix fastix 1073741824 Dez 23 17:58 mt_test.bin
            -rw-rw-r-- 1 fastix fastix 1073741824 Dez 23 17:42 open_ssl_test.bin
            -rw-rw-r-- 1 fastix fastix 1073741824 Dez 23 17:50 test.bin
            fastix@trainer:/tmp$ gzip *test.bin*
            fastix@trainer:/tmp$ ll *test.bin*
            -rw-rw-r-- 1 fastix fastix 1073915362 Dez 23 17:58 mt_test.bin.gz
            -rw-rw-r-- 1 fastix fastix 1073915392 Dez 23 17:42 open_ssl_test.bin.gz
            -rw-rw-r-- 1 fastix fastix 1073915637 Dez 23 17:50 test.bin.gz

            In allen drei Varianten war die Datei nach dem Packen größer. Da beim Packen nach irgendwelchen Regelmäßigkeiten bzw. Wiederholungen gesucht wird, die dann mehr oder weniger in eine mathematische Formel gepackt werden um die Datei zu verkleinern ist gzip bei solchen Zufallssachen ein ganz guter Test. So, wie das hier aussieht, sind weder mt_rand() noch rand() signifikant schlechter als openssl.

            Unstreitig hat die Erzeugung mit openssl_random_pseudo_bytes() am wenigsten Zeit gebraucht, mit rand() an längsten gedauert.

            Das sähe anders aus, wenn man Datenverkehr verschlüsselt und dafür Zufallszahlen braucht (echtes Verschlüsseln). Dann sind erstens die Datenmengen größer und zum zeitens lässt sich genauer bestimmen, was wann und in welcher Reihenfolge verschlüsselt war.

            Wie gesagt, beim Hashen (das ja auch noch nach Anfall, z.B. beim Eintragen oder Ändern eines Passwortes stattfindet, wobei sich die deterministischen Entropiequellen unvorhersehbar bzw. nicht nachvollziehbar entwickelt haben) kommt es nur darauf an, den Angreifer mit einer Vielzahl von verschiedenen Salts zu konfrontieren um ihn zu zwingen, die Rainbowtables für jeden genutzten Salt zu erzeugen, was einiges an Rechenleistung und damit Zeit und Speicherplatz erfordert.

            Beim Angriff auf Hashes nützt es dem Angreifer absolut nichts, wenn er die nächste Zufallszahl (mit einer, übrigens dem reinen Zufall gegenüber nur leicht erhöhten Wahrscheinlichkeit) richtig voraussagen kann. Er hat den Hash und damit den Salt ja so oder so in den Händen. Beim echten Verschlüsseln sieht das ganz anders aus.

            Jörg Reinholz

            1. Hakuna matata!

              Du hast recht, nachdem was man so liest, sind sich Kryptografen größtenteils darüber einig, dass Salts nicht von kryptografischen Zufallsgeneratoren erzeugt werden müssen. Ich muss meine Aussagen also revidieren.

              --
              “All right, then, I'll go to hell.” – Huck Finn
              1. Hi,

                Du hast recht, nachdem was man so liest, sind sich Kryptografen größtenteils darüber einig, dass Salts nicht von kryptografischen Zufallsgeneratoren erzeugt werden müssen.

                Hast du mal ne Quelle dazu? Ich finde nur gegenteiliges wie

                https://crackstation.net/hashing-security.htm#properhashing

                password_hash aus PHP 5.5 liest /dev/urandom meines Wissens. Das wird einen Grund haben.

                Harry

                1. password_hash aus PHP 5.5 liest /dev/urandom meines Wissens. Das wird einen Grund haben.

                  Dann lese mal im manual: (man 4 urandom)

                  When the entropy pool is empty, reads from /dev/random will block until additional environmental noise is gathered.

                  Zitat {
                  A read from the /dev/urandom device will not block waiting for more entropy.  As a result, if there is not sufficient entropy in the entropy pool, the returned values are theoretically vulnerable to a cryptographic attack on the  algorithms used by the driver.  Knowledge of how to do this is not available in the current unclassified literature, but it is theoretically possible that such an attack may exist.  If this is a concern in your application, use /dev/random instead.
                  }

                  Auf deutsch: /dev/random blockiert (liefert nichts mehr) wenn die entropie ausgeht. /dev/urandom liefert weiter, die gelieferten Werte sind aber nicht mehr wirklich zufällig, was theoretisch die Chance auf eine crytopgrafische Attacke eröffnet. Wenn das ein Hinderungsgrund sei, solle man auf /dev/random zurückgreifen.

                  Der Hinweis auf die theoretisch möglich werdende Attacke bezieht sich dann übrigens nicht auf das Password-Hashing, sondern ganz allgemein auf die Weiterverwendung. Beim Passwort-Hashing gibt es den Punkt, dass der Salt ja mit dem Hash bekannt wird - er steht im KLARTEXT drin und es kommt letztlich nur darauf an, dass es der Salt (möglichst) nicht mehrfach vorkommt, weil das dem Angreifer die Wiederverwendung einmal erzeugter Rainbow-Tables ermöglicht.

                  Das sieht z.B. bei der Generierung und Erneuerung eines session-keys ganz anders aus. Da geht es aber um Verschlüsselung und nicht um das Hashen.

                  Jörg Reinholz

                2. Hakuna matata!

                  Du hast recht, nachdem was man so liest, sind sich Kryptografen größtenteils darüber einig, dass Salts nicht von kryptografischen Zufallsgeneratoren erzeugt werden müssen.

                  Hast du mal ne Quelle dazu?

                  Zum Beispiel: http://stackoverflow.com/a/1645190/2007197

                  Die Argumentation von Jörg ist ja auch schlüssig. Kryptografisch sichere Zufallszahlen sollen es unmöglich für Angreifer machen Rückschlüsse auf eine errechnete Psuedozufallszahl zu ziehen. Das ist dann eine erforderliche Eigenschaft, wenn damit ein Geheimnis berechnet werden soll. Salts erfordern ihrerseits aber gar keine Geheimhaltung, sondern nur Einmaligkeit.

                  --
                  “All right, then, I'll go to hell.” – Huck Finn