PHP bester Weg zu MD5 mehrdimensionalen Array?

120

Was ist der beste Weg, um ein MD5 (oder einen anderen Hash) eines mehrdimensionalen Arrays zu generieren?

Ich könnte leicht eine Schleife schreiben, die jede Ebene des Arrays durchläuft, jeden Wert zu einer Zeichenfolge verkettet und einfach die MD5 für die Zeichenfolge ausführt.

Dies scheint jedoch bestenfalls umständlich zu sein, und ich fragte mich, ob es eine funky Funktion gab, die ein mehrdimensionales Array aufnehmen und es hashen würde.

Peter John
quelle

Antworten:

259

(Copy-n-Paste-fähige Funktion unten)

Wie bereits erwähnt, funktioniert Folgendes.

md5(serialize($array));

Es ist jedoch erwähnenswert, dass json_encode (ironischerweise) merklich schneller arbeitet:

md5(json_encode($array));

Tatsächlich ist die Geschwindigkeitssteigerung hier zweifach, da (1) json_encode allein schneller als serialisiert ist und (2) json_encode eine kleinere Zeichenfolge erzeugt und daher weniger für md5 zu verarbeiten ist.

Bearbeiten: Hier sind Beweise, um diese Behauptung zu stützen:

<?php //this is the array I'm using -- it's multidimensional.
$array = unserialize('a:6:{i:0;a:0:{}i:1;a:3:{i:0;a:0:{}i:1;a:0:{}i:2;a:3:{i:0;a:0:{}i:1;a:0:{}i:2;a:0:{}}}i:2;s:5:"hello";i:3;a:2:{i:0;a:0:{}i:1;a:0:{}}i:4;a:1:{i:0;a:1:{i:0;a:1:{i:0;a:1:{i:0;a:1:{i:0;a:1:{i:0;a:0:{}}}}}}}i:5;a:5:{i:0;a:0:{}i:1;a:4:{i:0;a:0:{}i:1;a:0:{}i:2;a:3:{i:0;a:0:{}i:1;a:0:{}i:2;a:0:{}}i:3;a:6:{i:0;a:0:{}i:1;a:3:{i:0;a:0:{}i:1;a:0:{}i:2;a:3:{i:0;a:0:{}i:1;a:0:{}i:2;a:0:{}}}i:2;s:5:"hello";i:3;a:2:{i:0;a:0:{}i:1;a:0:{}}i:4;a:1:{i:0;a:1:{i:0;a:1:{i:0;a:1:{i:0;a:1:{i:0;a:1:{i:0;a:0:{}}}}}}}i:5;a:5:{i:0;a:0:{}i:1;a:3:{i:0;a:0:{}i:1;a:0:{}i:2;a:3:{i:0;a:0:{}i:1;a:0:{}i:2;a:0:{}}}i:2;s:5:"hello";i:3;a:2:{i:0;a:0:{}i:1;a:0:{}}i:4;a:1:{i:0;a:1:{i:0;a:1:{i:0;a:1:{i:0;a:1:{i:0;a:1:{i:0;a:0:{}}}}}}}}}}i:2;s:5:"hello";i:3;a:2:{i:0;a:0:{}i:1;a:0:{}}i:4;a:1:{i:0;a:1:{i:0;a:1:{i:0;a:1:{i:0;a:1:{i:0;a:1:{i:0;a:0:{}}}}}}}}}');

//The serialize test
$b4_s = microtime(1);
for ($i=0;$i<10000;$i++) {
    $serial = md5(serialize($array));
}
echo 'serialize() w/ md5() took: '.($sTime = microtime(1)-$b4_s).' sec<br/>';

//The json test
$b4_j = microtime(1);
for ($i=0;$i<10000;$i++) {
    $serial = md5(json_encode($array));
}
echo 'json_encode() w/ md5() took: '.($jTime = microtime(1)-$b4_j).' sec<br/><br/>';
echo 'json_encode is <strong>'.( round(($sTime/$jTime)*100,1) ).'%</strong> faster with a difference of <strong>'.($sTime-$jTime).' seconds</strong>';

JSON_ENCODE ist durchweg über 250% (2,5x) schneller (oft über 300%) - dies ist kein trivialer Unterschied. Die Ergebnisse des Tests mit diesem Live-Skript können Sie hier sehen:

Nun ist zu beachten, dass Array (1,2,3) ein anderes MD5 als Array (3,2,1) erzeugt. Wenn dies NICHT das ist, was Sie wollen. Versuchen Sie den folgenden Code:

//Optionally make a copy of the array (if you want to preserve the original order)
$original = $array;

array_multisort($array);
$hash = md5(json_encode($array));

Bearbeiten: Es gab einige Fragen, ob das Umkehren der Reihenfolge zu den gleichen Ergebnissen führen würde. Also habe ich das hier ( richtig ) gemacht:

Wie Sie sehen können, sind die Ergebnisse genau gleich. Hier ist der ( korrigierte ) Test, der ursprünglich von jemandem erstellt wurde, der mit Drupal verwandt ist :

Und zum guten Teil, hier ist eine Funktion / Methode, die Sie kopieren und einfügen können (getestet in 5.3.3-1ubuntu9.5):

function array_md5(Array $array) {
    //since we're inside a function (which uses a copied array, not 
    //a referenced array), you shouldn't need to copy the array
    array_multisort($array);
    return md5(json_encode($array));
}
Nathan JB
quelle
47
LOL! "Ja wirklich?" Ich wurde für "Über" -Optimierung gestimmt? In Wirklichkeit ist die Serialisierung von PHP erheblich langsamer. Ich werde meine Antwort mit Beweisen aktualisieren ...
Nathan JB
19
Was Nathan hier getan hat, ist wertvoll, auch wenn man den Wert nicht sehen kann. In einigen Situationen, die außerhalb unseres Kontexts liegen, kann dies eine wertvolle Optimierung sein.
Mikrooptimierung
13
Ich bin nicht derjenige für Mikrooptimierung, aber wo es eine dokumentierte Leistungssteigerung für keine zusätzliche Arbeit gibt, warum nicht?
Stoßstange
2
Eigentlich sieht es so aus, als ob es davon abhängt, wie tief das Array ist. Ich brauche etwas, das so schnell wie möglich ausgeführt werden muss, und während Ihr POC anzeigt, dass json_encode () ~ 300% schneller ist, wurde die Variable $ array in Ihrem Code in meinen Anwendungsfall geändert serialize() w/ md5() took: 0.27773594856262 sec json_encode() w/ md5() took: 0.34809803962708 sec json_encode is (79.8%) faster with a difference of (-0.070362091064453 seconds)(die Vorgängerberechnung) ist offensichtlich falsch). Mein Array ist bis zu 2 Ebenen tief. Denken Sie also daran, dass Ihre Laufleistung (wie üblich) variieren kann.
Samitny
3
Okay, ich verstehe nicht, warum Nathans Antwort nicht die beste Antwort ist. Im Ernst, verwenden Sie serialize und ärgern Sie Ihre Benutzer mit einer immensen langsamen Site. Episch +1 @ NathanJ.Brauer!
ReSpawN
168
md5(serialize($array));
Brock Batsell
quelle
13
Wenn Sie aus irgendeinem Grund mit dem Hash (Fingerabdruck) übereinstimmen möchten, sollten Sie in Betracht ziehen, das Array "sort" oder "ksort" zu sortieren. Möglicherweise ist auch eine Implementierung von Scrubbing / Cleaning erforderlich
Farinspace
9
Serialize ist soooooooo viel langsamer als json_encode aus der zweiten Antwort. Machen Sie Ihrem Server ein Vergnügen und verwenden Sie json_encode! :)
s3m3n
3
Anscheinend müssen Sie Ihr eigenes Array vergleichen, um herauszufinden, ob Sie json_encode verwenden oder serialisieren sollten. Je nach Array ist dies unterschiedlich.
Ligemer
Ich glaube, es ist ein falscher Weg, bitte überprüfen Sie meine Erklärung unten.
TermiT
1
@joelpittet - Nein. Beide Beispiele in diesem Drupal-Link weisen Fehler auf. Siehe die Kommentare in meiner Antwort unten. ;) ZB dl.dropboxusercontent.com/u/4115701/Screenshots/…
Nathan JB
26

Ich schließe mich einer sehr überfüllten Gruppe an, indem ich antworte, aber es gibt eine wichtige Überlegung, die keine der vorhandenen Antworten anspricht. Der Wert von json_encode()und serialize()beide hängen von der Reihenfolge der Elemente im Array ab!

Hier sind die Ergebnisse , wenn die Arrays nicht sortiert und sortiert werden, sondern auf zwei Arrays mit identischen Werten, die jedoch in einer anderen Reihenfolge hinzugefügt wurden (Code am Ende des Beitrags) :

    serialize()
1c4f1064ab79e4722f41ab5a8141b210
1ad0f2c7e690c8e3cd5c34f7c9b8573a

    json_encode()
db7178ba34f9271bfca3a05c5dddf502
c9661c0852c2bd0e26ef7951b4ca9e6f

    Sorted serialize()
1c4f1064ab79e4722f41ab5a8141b210
1c4f1064ab79e4722f41ab5a8141b210

    Sorted json_encode()
db7178ba34f9271bfca3a05c5dddf502
db7178ba34f9271bfca3a05c5dddf502

Daher würden ich zwei Methoden empfehlen, um ein Array zu hashen:

// You will need to write your own deep_ksort(), or see
// my example below

md5(   serialize(deep_ksort($array)) );

md5( json_encode(deep_ksort($array)) );

Die Auswahl json_encode()oder serialize()sollte durch Testen des von Ihnen verwendeten Datentyps festgelegt werden . Wenn der Code nach meinen eigenen Tests mit rein textuellen und numerischen Daten nicht tausendmal in einer engen Schleife ausgeführt wird, ist der Unterschied nicht einmal ein Benchmarking wert. Ich persönlich verwende json_encode()für diese Art von Daten.

Hier ist der Code, der zum Generieren des obigen Sortiertests verwendet wird:

$a = array();
$a['aa'] = array( 'aaa'=>'AAA', 'bbb'=>'ooo', 'qqq'=>'fff',);
$a['bb'] = array( 'aaa'=>'BBBB', 'iii'=>'dd',);

$b = array();
$b['aa'] = array( 'aaa'=>'AAA', 'qqq'=>'fff', 'bbb'=>'ooo',);
$b['bb'] = array( 'iii'=>'dd', 'aaa'=>'BBBB',);

echo "    serialize()\n";
echo md5(serialize($a))."\n";
echo md5(serialize($b))."\n";

echo "\n    json_encode()\n";
echo md5(json_encode($a))."\n";
echo md5(json_encode($b))."\n";



$a = deep_ksort($a);
$b = deep_ksort($b);

echo "\n    Sorted serialize()\n";
echo md5(serialize($a))."\n";
echo md5(serialize($b))."\n";

echo "\n    Sorted json_encode()\n";
echo md5(json_encode($a))."\n";
echo md5(json_encode($b))."\n";

Meine schnelle Implementierung von deep_ksort () passt in diesen Fall, aber überprüfen Sie sie, bevor Sie sie für Ihre eigenen Projekte verwenden:

/*
* Sort an array by keys, and additionall sort its array values by keys
*
* Does not try to sort an object, but does iterate its properties to
* sort arrays in properties
*/
function deep_ksort($input)
{
    if ( !is_object($input) && !is_array($input) ) {
        return $input;
    }

    foreach ( $input as $k=>$v ) {
        if ( is_object($v) || is_array($v) ) {
            $input[$k] = deep_ksort($v);
        }
    }

    if ( is_array($input) ) {
        ksort($input);
    }

    // Do not sort objects

    return $input;
}
dotancohen
quelle
10

Die Antwort hängt stark von den Datentypen der Array-Werte ab. Für große Saiten verwenden Sie:

md5(serialize($array));

Verwenden Sie für kurze Zeichenfolgen und Ganzzahlen:

md5(json_encode($array));

4 integrierte PHP-Funktionen können das Array in einen String umwandeln: serialize () , json_encode () , var_export () , print_r () .

Hinweis: Die Funktion json_encode () wird verlangsamt, während assoziative Arrays mit Zeichenfolgen als Werte verarbeitet werden. In diesem Fall sollten Sie die Funktion serialize () verwenden.

Testergebnisse für mehrdimensionale Arrays mit md5-Hashes (32 Zeichen) in Schlüsseln und Werten:

Test name       Repeats         Result          Performance     
serialize       10000           0.761195 sec    +0.00%
print_r         10000           1.669689 sec    -119.35%
json_encode     10000           1.712214 sec    -124.94%
var_export      10000           1.735023 sec    -127.93%

Testergebnis für numerisches mehrdimensionales Array:

Test name       Repeats         Result          Performance     
json_encode     10000           1.040612 sec    +0.00%
var_export      10000           1.753170 sec    -68.47%
serialize       10000           1.947791 sec    -87.18%
print_r         10000           9.084989 sec    -773.04%

Assoziative Array -Testquelle . Numerische Array -Testquelle .

Alexander Yancharuk
quelle
Können Sie bitte erklären, was große und kurze Saiten sind ?
AL
1
@AL kurze Zeichenfolgen - Zeichenfolgen, die weniger als 25-30 Zeichen enthalten. große Saiten - alle mit mehr als 25-30 Zeichen.
Alexander Yancharuk
7

Abgesehen von Brocks hervorragender Antwort (+1) können Sie mit jeder anständigen Hashing-Bibliothek den Hash in Schritten aktualisieren, sodass Sie in der Lage sein sollten, mit jeder Zeichenfolge nacheinander zu aktualisieren, anstatt eine riesige Zeichenfolge aufzubauen.

Sehen: hash_update

Chris Jester-Young
quelle
Es ist erwähnenswert, dass diese Methode ineffizient ist, wenn Sie mit winzigen Fragmenten aktualisieren. Es ist jedoch gut für große Teile großer Dateien.
Wrygiel
@rygiel Das stimmt nicht. Bei MD5 erfolgt die Komprimierung immer in 64-Byte-Blöcken (unabhängig von der Größe Ihrer "großen Blöcke"). Wenn Sie noch keinen Block gefüllt haben, erfolgt keine Verarbeitung, bis der Block gefüllt ist. (Wenn Sie den Hash finalisieren, wird der letzte Block im Rahmen der endgültigen Verarbeitung zu einem vollständigen Block aufgefüllt.) Weitere Hintergrundinformationen finden Sie in der Merkle-Damgard-Konstruktion (auf der MD5, SHA-1 und SHA-2 basieren) ).
Chris Jester-Young
Du hast recht. Ich wurde durch einen Kommentar auf einer anderen Seite total irregeführt.
Wrygiel
@wrygiel Deshalb lohnt es sich, selbst zu recherchieren, wenn Sie einer Idee folgen, die "im Internet gefunden" wurde. ;-) In diesem Sinne war dieser letzte Kommentar für mich leicht zu schreiben, da ich MD5 vor einigen Jahren tatsächlich von Grund auf neu implementiert habe (um meine Programmierkenntnisse im Schema zu üben), sodass ich seine Funktionsweise sehr gut kenne.
Chris Jester-Young
Genau das will ich. Das Verschieben und Kopieren eines großen Datenwagens im Speicher ist manchmal nicht akzeptabel. Wie bei anderen Antworten ist die Verwendung von serialize () eine sehr schlechte Idee in Bezug auf die Leistung. Diese API fehlt jedoch immer noch, wenn ich nur einen Teil des Strings von einem bestimmten Offset aus hashen möchte.
Jianwu Chen
4
md5(serialize($array));

Funktioniert, aber der Hash ändert sich abhängig von der Reihenfolge des Arrays (das spielt jedoch möglicherweise keine Rolle).

Max Wheeler
quelle
3

Beachten Sie dies serializeund json_encodeverhalten Sie sich anders, wenn es um numerische Arrays geht, bei denen die Schlüssel nicht bei 0 beginnen, oder um assoziative Arrays. json_encodespeichert solche Arrays als Object, json_decodegibt also ein zurück Object, wobei unserializeein Array mit genau denselben Schlüsseln zurückgegeben wird.

Willem-Jan
quelle
3

Ich denke, das könnte ein guter Tipp sein:

Class hasharray {

    public function array_flat($in,$keys=array(),$out=array()){
        foreach($in as $k => $v){
            $keys[] = $k; 
            if(is_array($v)){
                $out = $this->array_flat($v,$keys,$out);
            }else{
                $out[implode("/",$keys)] = $v;
            }
            array_pop($keys);
        }
        return $out;  
    }

    public function array_hash($in){
        $a = $this->array_flat($in);
        ksort($a);
        return md5(json_encode($a));
    }

}

$h = new hasharray;
echo $h->array_hash($multi_dimensional_array);
Andrej Pandovich
quelle
2

Wichtiger Hinweis zu serialize()

Ich empfehle nicht, es als Teil der Hashing-Funktion zu verwenden, da es für die folgenden Beispiele unterschiedliche Ergebnisse zurückgeben kann. Überprüfen Sie das folgende Beispiel:

Einfaches Beispiel:

$a = new \stdClass;
$a->test = 'sample';

$b = new \stdClass;
$b->one = $a;
$b->two = clone $a;

Produziert

"O:8:"stdClass":2:{s:3:"one";O:8:"stdClass":1:{s:4:"test";s:6:"sample";}s:3:"two";O:8:"stdClass":1:{s:4:"test";s:6:"sample";}}"

Aber der folgende Code:

<?php

$a = new \stdClass;
$a->test = 'sample';

$b = new \stdClass;
$b->one = $a;
$b->two = $a;

Ausgabe:

"O:8:"stdClass":2:{s:3:"one";O:8:"stdClass":1:{s:4:"test";s:6:"sample";}s:3:"two";r:2;}"

Also anstelle des zweiten Objekts PHP einfach den Link "r: 2;" zur ersten Instanz. Es ist definitiv eine gute und korrekte Methode, Daten zu serialisieren, aber es kann zu Problemen mit Ihrer Hashing-Funktion führen.

TermiT
quelle
2
// Convert nested arrays to a simple array
$array = array();
array_walk_recursive($input, function ($a) use (&$array) {
    $array[] = $a;
});

sort($array);

$hash = md5(json_encode($array));

----

These arrays have the same hash:
$arr1 = array(0 => array(1, 2, 3), 1, 2);
$arr2 = array(0 => array(1, 3, 2), 1, 2);
ymakux
quelle
1

Es gibt mehrere Antworten, die sagen, dass json_code verwendet werden soll.

Aber json_encode funktioniert nicht gut mit der Zeichenfolge iso-8859-1. Sobald ein spezielles Zeichen vorhanden ist, wird die Zeichenfolge abgeschnitten.

Ich würde raten, var_export zu verwenden:

md5(var_export($array, true))

nicht so langsam wie serialize, nicht so fehlerhaft wie json_encode

Bruno
quelle
Nicht so schnell, die beste Option ist die Verwendung von md4, var_export ist auch langsam
user956584
0

Derzeit md5(serialize($array));funktioniert die am besten bewertete Antwort nicht gut mit Objekten.

Betrachten Sie den Code:

 $a = array(new \stdClass());
 $b = array(new \stdClass());

Obwohl Arrays unterschiedlich sind (sie enthalten unterschiedliche Objekte), haben sie bei der Verwendung denselben Hash md5(serialize($array));. Dein Hash ist also nutzlos!

Um dieses Problem zu vermeiden, können Sie Objekte spl_object_hash()vor der Serialisierung durch das Ergebnis von ersetzen . Sie sollten dies auch rekursiv tun, wenn Ihr Array mehrere Ebenen hat.

Der folgende Code sortiert Arrays auch nach Schlüsseln, wie von dotancohen vorgeschlagen.

function replaceObjectsWithHashes(array $array)
{
    foreach ($array as &$value) {
        if (is_array($value)) {
            $value = $this->replaceObjectsInArrayWithHashes($value);
        } elseif (is_object($value)) {
            $value = spl_object_hash($value);
        }
    }
    ksort($array);
    return $array;
}

Jetzt können Sie verwenden md5(serialize(replaceObjectsWithHashes($array))).

(Beachten Sie, dass das Array in PHP vom Werttyp ist. replaceObjectsWithHashesÄndern Sie also NICHT das ursprüngliche Array.)

Damian Polac
quelle
0

Ich habe die Lösung oben nicht so leicht gesehen, deshalb wollte ich eine einfachere Antwort beitragen. Für mich bekam ich den gleichen Schlüssel, bis ich ksort (Schlüsselsortierung) verwendete:

Zuerst mit Ksort sortiert, dann sha1 mit einem json_encode ausgeführt:

ksort($array)
$hash = sha1(json_encode($array) //be mindful of UTF8

Beispiel:

$arr1 = array( 'dealer' => '100', 'direction' => 'ASC', 'dist' => '500', 'limit' => '1', 'zip' => '10601');
ksort($arr1);

$arr2 = array( 'direction' => 'ASC', 'limit' => '1', 'zip' => '10601', 'dealer' => '100', 'dist' => '5000');
ksort($arr2);

var_dump(sha1(json_encode($arr1)));
var_dump(sha1(json_encode($arr2)));

Ausgabe geänderter Arrays und Hashes:

string(40) "502c2cbfbe62e47eb0fe96306ecb2e6c7e6d014c"
string(40) "b3319c58edadab3513832ceeb5d68bfce2fb3983"
Mike Q.
quelle