Warum erzeugt crypt / blowfish denselben Hash mit zwei verschiedenen Salzen?
Diese Frage hat zu tun mit PHP Implementierung von crypt()
. Für diese Frage, die ersten 7 Zeichen des Salzes werden nicht gezählt, so dass ein Salz '$2a$07$a
' sagt, Sie haben eine Länge von 1, da es nur 1 Zeichen von Salz und sieben Zeichen der meta-Daten.
Wenn die Verwendung von Salz-Zeichenfolgen, die länger als 22 Zeichen, es ist keine änderung in der hash generiert (d.h., abschneiden), und bei der Verwendung von Zeichenketten, die kürzer als 21 Zeichen der salt wird automatisch aufgefüllt werden (mit '$
' Zeichen, offenbar); dies ist Recht einfach. Allerdings, wenn gegeben, eine Salz 20 Zeichen und ein Salz 21 Zeichen, wo die beiden sind identisch, außer für das Letzte Zeichen der 21-Länge Salz, beide Hash-strings identisch sein wird. Ein Salz 22 Zeichen lang, die identisch ist mit der 21 Länge Salz, außer für den letzten Charakter, der hash wird wieder anders werden.
Beispiel In Code:
$foo = 'bar';
$salt_xx = '$2a$07$';
$salt_19 = $salt_xx . 'b1b2ee48991281a439d';
$salt_20 = $salt_19 . 'a';
$salt_21 = $salt_20 . '2';
$salt_22 = $salt_21 . 'b';
var_dump(
crypt($foo, $salt_19),
crypt($foo, $salt_20),
crypt($foo, $salt_21),
crypt($foo, $salt_22)
);
Produzieren:
string(60) "$2a$07$b1b2ee48991281a439d$$.dEUdhUoQXVqUieLTCp0cFVolhFcbuNi"
string(60) "$2a$07$b1b2ee48991281a439da$.UxGYN739wLkV5PGoR1XA4EvNVPjwylG"
string(60) "$2a$07$b1b2ee48991281a439da2.UxGYN739wLkV5PGoR1XA4EvNVPjwylG"
string(60) "$2a$07$b1b2ee48991281a439da2O4AH0.y/AsOuzMpI.f4sBs8E2hQjPUQq"
Warum ist das so?
EDIT:
Einige Benutzer darauf hingewiesen werden, dass es einen Unterschied in der Gesamt-string, das ist wahr. In salt_20
-, offset - (28, 4) ist da$.
während in salt_21
-, offset - (28, 4) ist da2.
; es ist jedoch wichtig zu beachten, dass die generierten Zeichenkette enthält den Hashwert, das Salz, sowie eine Anleitung zum generieren des Salzes (d.h. $2a$07$
); der Teil, in dem der Unterschied Auftritt, ist in der Tat noch das Salz. Der eigentliche hash ist unverändert als UxGYN739wLkV5PGoR1XA4EvNVPjwylG
.
So, das ist in der Tat kein Unterschied in der hash produziert, aber einen Unterschied in der Salz verwendet zum speichern der hash, das ist genau das problem: zwei Salze generieren, die denselben hash.
Rembmer: die Ausgabe erfolgt im folgenden format:
"$2a$##$saltsaltsaltsaltsaltsaHASHhashHASHhashHASHhashHASHhash"
// ^ Hash Starts Here, offset 28,32
wobei ## die log-base-2 die Bestimmung der Anzahl der Iterationen, die der Algorithmus läuft für
Edit 2:
In die Kommentare, es wurde gewünscht, dass ich post Sie einige zusätzliche Informationen, wie der Benutzer nicht reproduzieren konnten meine Ausgabe. Ausführung des folgenden Codes:
var_dump(
PHP_VERSION,
PHP_OS,
CRYPT_SALT_LENGTH,
CRYPT_STD_DES,
CRYPT_EXT_DES,
CRYPT_MD5,
CRYPT_BLOWFISH
);
Erzeugt die folgende Ausgabe:
string(5) "5.3.0"
string(5) "WINNT"
int(60)
int(1)
int(1)
int(1)
int(1)
Hoffe, das hilft.
InformationsquelleAutor der Frage Dereleased | 2010-02-08
Du musst angemeldet sein, um einen Kommentar abzugeben.
Nach einigen Experimenten bin ich zu dem Schluss, dass dies aufgrund der Art, wie das Salz verarbeitet wird. Das Salz ist nicht als Literalzeichen im text, sondern eher eine base64-codierte Zeichenfolge, so dass die 22-Byte-salt-Daten würden tatsächlich repräsentieren einen 16-byte-string (
floor(22 * 24 /32) == 16
) Salz. Das "Gotcha!" mit dieser Umsetzung ist jedoch, dass sich, wie bei Unix-crypt, verwendet es eine "nicht-standard" base64-alphabet. Um genau zu sein, verwendet es dieses alphabet:Den 65th Zeichen, '
$
', ist die Polsterung Charakter.Nun, die
crypt()
Funktion zu sein scheint, die fähig sind, ein Salz von beliebiger Länge weniger als oder gleich Ihrer maximalen, still und leise Umgang mit Inkonsistenzen in der base64, indem er alle Daten, die nicht einem anderen vollen byte. Die crypt-Funktion wird scheitern, wenn Sie es passieren Zeichen in das Salz, das nicht Teil seiner base64-alphabet, das nur bestätigt diese Theorie der seinen Betrieb.Nehmen Sie eine imaginäre salt '
1234
'. Dies ist perfekt base64 konsistent, es stellt 24-bit-Daten, also 3 bytes, und nicht tragen keine Daten, muss verworfen werden. Dies ist ein Salz, dessenLen Mod 4
null ist. Anhängen von beliebigen Zeichen, das Salz, und es wird ein 5-Zeichen-salt, undLen Mod 4
ist jetzt 1. Jedoch, diese zusätzlichen Zeichen repräsentiert nur sechs bits von Daten, und kann daher nicht umgewandelt werden in ein anderes volles byte, so wird es verworfen.Somit für zwei beliebige Salze A und B, wobei
Das eigentliche Salz verwendet
crypt()
um den hash zu berechnen, wird in der Tat identisch sein. Als Beweis, ich bin auch einige Beispiel-PHP-code kann verwendet werden, um dies zu zeigen. Das Salz ständig rotiert in einemseminicht-zufällige Weise (basierend auf einer randomish segment des whirlpool-hash aus der aktuellen Zeit in Mikrosekunden), und die Daten werden gehasht (in der Folge kurz$seed
) ist einfach die aktuelle Unix-Epoche Zeit.Und dies erzeugt eine Ausgabe ähnlich der folgenden
Abschluss? Eine doppelte. Erstens, es wie vorgesehen funktioniert, und zweitens wissen, Ihre eigenen Salz-oder don ' T roll your own Salz.
InformationsquelleAutor der Antwort Dereleased
Große Antwort, und klare Erklärung. Aber es scheint mir, es ist entweder ein bug in der Implementierung oder einige weitere Erklärung von der Absicht gebraucht wird {die Kommentare zu dem post erklären, warum es kein Fehler}. Die aktuelle php-Dokumentation Staaten:
Dies ist konsistent mit dem, was gesagt wurde und hier gezeigt. Leider ist die Dokumentation beschreibt nicht die return-Wert sehr sinnvoll:
Aber, wie gezeigt, in der Antwort von Dereleasedwenn die input-salt-string ist gültig, die Ausgabe besteht aus dem Eingangs-Salz gepolstert, auf eine Feste Länge, die mit ' $ ' - Zeichen, mit dem 32-stellige berechnete hash-Wert angehängt. Leider, das Salz in der Folge ist gepolstert, das auf nur 21 base64 Ziffern, nicht 22! Dies wird durch die letzten drei Zeilen, die Antwort, wo wir sehen, ein '$' für 20 stellen, ohne'$', 21, und wenn es 22 base64 Ziffern in das Salz, das erste Zeichen von dem hash-Ergebnis ersetzt die 22-stellige von der input-Salz. Die Funktion ist noch nutzbar, weil der vollständige Wert berechnet er verfügbar ist, um den Anrufer als
substr(crypt($pw,$salt), 28, 32)
und der Anrufer bereits weiß, die komplette salt-Wert, weil es übergeben Sie die Zeichenfolge als argument. Aber es ist sehr schwierig zu verstehen, warum der Rückgabewert ist so konzipiert, dass es kann Ihnen nur 126 bit der 128-bit-salt-Wert. In der Tat, es ist schwer zu verstehen, warum es beinhaltet die input-Salz; aber das weglassen von 2 bits es ist wirklich unergründlich.Hier ein kleines snippet zeigt, dass der 22 base64-stellige trägt nur zwei weitere bits, um das Salz tatsächlich in der Berechnung (es gibt nur 4 verschiedene hashes erzeugt):
Die Gruppierung der identische hash-Werte zeigt auch, dass die Zuordnung des Alphabets tatsächlich verwendet wird, ist wahrscheinlich, wie hier geschrieben, dann eher in der Reihenfolge, die in der anderen Antwort.
Vielleicht die Schnittstelle wurde auf diese Weise entworfen für eine Art von Kompatibilität, und vielleicht, weil es schon versendet diese Weise nicht geändert werden kann. {der erste Kommentar zu dem post erklärt, warum das interface ist auf diese Weise}. Aber sicherlich die Dokumentation sollte, um zu erklären, was Los ist. Nur für den Fall, der Fehler könnte behoben einige Tage, vielleicht wäre es am sichersten, Sie erhalten den hash-Wert mit:
Als ein letzter Hinweis, während die Erklärung, warum der hash-Wert wird wiederholt, wenn die Anzahl der base64-Ziffern angegeben
mod 4 == 1
macht Sinn in Bezug auf, warum der code könnte sich Verhalten, die Art und Weise, er erklärt nicht, warum schreiben Sie den code so eine gute Idee war. Der code könnte und wohl auch sollte die bits aus, die eine base64-stellige, macht eine partielle byte, bei der Berechnung der hash-Werte, anstatt einfach zu verwerfen. Wenn der code, der geschrieben worden war, so scheint es wahrscheinlich das problem mit dem verlieren, dem 22. Stelle das Salz in der Ausgabe nicht erschienen. {Wie die Kommentare zu dem post zu erklären, obwohl die 22-stellige überschrieben wird, wird die Ziffer der hash ist, überschreibt es wird nur eine der vier möglichen Werte[.Oeu]
und diese sind nur signifikante Werte für die 22-stellige. Wenn die 22-stellige ist nicht einer der vier Werte, es wird ersetzt durch die einer dieser vier, der produziert den gleichen hash.}Im Licht der Kommentare, scheint es klar, es ist kein bug, einfach nur unglaublich wortkarg Dokumentation 🙂 Da bin ich nicht ein Kryptologe, ich kann nicht sagen, dass das bei keiner Behörde, aber es scheint mir, dass es eine Schwäche des Algorithmus, der eine 21-stellige Salz anscheinend kann produzieren alle möglichen hash-Werte, während eine 22-stellige Salz Grenzen die erste Ziffer der hash nur einen der vier Werte.
InformationsquelleAutor der Antwort sootsnoot
Sieht aus wie die Ausgänge sind tatsächlich anders. (da$, vs da2) für das Ergebnis der salt_20 und salt_21.
InformationsquelleAutor der Antwort H. Green
Aus meiner Untersuchung schien es, dass das Salz ist immer 22 Zeichen und der hash-offset ist 29, nicht 28, so dass es 31 Zeichen in der Länge, nicht 32. Ich lief diesen code:
Waren die Ergebnisse:
Dies deutet darauf hin, dass die Salz-Teil des zurückgegebenen hash gespeichert wird nur bits, also kann es sich nicht immer entsprechend Ihrer Eingaben Salz. Der Vorteil ist, dass der hash kann unverändert verwendet werden, da das Salz bei der überprüfung. Also, du bist besser dran, nur das speichern des hash zurückgegeben
crypt()
und nie die input-Salz, die Sie verwenden zunächst. In der Praxis:und
Ihre eigenen Rollen Salze ist nicht ein problem, und das wissen von Ihnen (durch dieses, ich nehme an, Sie meinte Sie separat speichern, um die hash) ist nicht erforderlich, wenn Sie die Speicherung
crypt()
's-Ausgabe, wie Sie ist.InformationsquelleAutor der Antwort Walf