hmac.new python 3.4 mit Dictionary anwenden, Vergleich PHP

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Serpens66
User
Beiträge: 259
Registriert: Montag 15. Dezember 2014, 00:31

Hier ist ein Beispiel mit PHP und python 2: http://www.php2python.com/wiki/function.hash-hmac/
Warum ist in PHP das folgende:
[codebox=php file=Unbenannt.php]echo hash_hmac("sha256","a", "1");[/code]
dasselbe wie (nun in python 3.4, bei dem man encode() nicht vergessen darf)

Code: Alles auswählen

import hmac,hashlib
key='1'
data='a'
print (hmac.new(key.encode(), data.encode(), hashlib.sha256).hexdigest())
Aber sobald ich in Python nun ein dictionary anstelle eines Strings als data nehme, ist es nicht mehr dasselbe?

Also ich habe nun:
[codebox=php file=Unbenannt.php]
$params['api_key'] = "789";
$params['nonce'] = 555;
echo hash_hmac("sha256",json_encode($params), "1");
[/code]
Aber das ist nicht mehr dasselbe wie:

Code: Alles auswählen

params = {"api_key":"789","nonce":555}
print (hmac.new("1".encode(), json.dumps(params).encode(), hashlib.sha256).hexdigest())
Das Endergebnis von Python soll dasselbe Ergebnis sein, wie in PHP. Wie erreiche ich das?

Zuerst dachte ich, dass es eventuell an der Reihenfolge liegen kann, mit der json.dumps die dictionary Einträge umpackt. Aber selbst wenn ich dafür sorge, dass in PHP und Python dieselbe Reiehenfolge eingehalten wird, kommt nicht dasselbe Ergebnis raus..
Merkwürdig ist allerdings, dass in PHP immer dasselbe Ergebnis bei rauskommt, während es sich in Python ändert, je nach Reihenfolge der Einträge.
BlackJack

@Serpens66: Die selben Daten können auf unterschiedliche Weise als JSON serialisiert werden. Es gibt AFAIK auch keine kanonische Form bei JSON, damit eignet sich das ganz einfach nicht für das was Du da anscheinend machen willst.

Das sich das Ergebnis ändert wenn die Daten nicht exakt sie selben sind ist alles andere als merkwürdig.
Serpens66
User
Beiträge: 259
Registriert: Montag 15. Dezember 2014, 00:31

BlackJack hat geschrieben:@Serpens66: Die selben Daten können auf unterschiedliche Weise als JSON serialisiert werden. Es gibt AFAIK auch keine kanonische Form bei JSON, damit eignet sich das ganz einfach nicht für das was Du da anscheinend machen willst.

Das sich das Ergebnis ändert wenn die Daten nicht exakt sie selben sind ist alles andere als merkwürdig.
Danke für deine Antwort. Hatte gehofft, dass diese vereinfachte Fragestellung ausreicht, aber es fehlen wohl mal wieder wichtige Informationen? :)

Dann nun ein wenig ausführlicher:
Ich möchte mal wieder auf eine API zugreifen, welche eine Authentifizierung mithilfe API Key und Secret verlangt. Bei jeder anderen Seite habe ich das bisher noch hinbekommen (entweder weil kein dictionary in der signatur beinhaltet war, oder weil vorgegeben war, wie ich es zu sortieren habe), aber diesmal beiße ich mir die Zähne aus.

In der Dokumentation steht dazu folgendes:
Authenticating Your Request

To authenticate a request with your API key/secret pair, you must include the following parameters in your POST parameters or the JSON PAYLOAD of your request:

api_key (string) - The API key that you generated.
nonce (int) - A random integer. Each request must have a higher nonce than the last one. You can use the current UNIX timestamp for example.
signature (string) - An HMAC-SHA256 signature of the JSON-encoded parameters of the request, signed using the API secret that was generated together with the api_key. These parameters include the api_key and nonce. This signature should then be added to the request parameters.

We know that generating a signature might be a bit intimidating if you're doing it for the first time, so please see the following examples:
[codebox=php file=Unbenannt.php]// PHP Example

// we add our public key and nonce to whatever parameters we are sending
$commands['side'] = 'sell';
$commands['type'] = 'stop';
$commands['api_key'] = $api_key;
$commands['nonce'] = time();

// create the signature
$signature = hash_hmac('sha256', json_encode($commands), $api_secret);

// add signature to request parameters
$commands['signature'] = $signature;
[/code]

Code: Alles auswählen

# Python Example
	
import hashlib
import hmac
	
// we add our public key and nonce to whatever parameters we are sending
params = {'currency': 'eur', 'price': 200, 'api_key': api_key, 'nonce': time.time()}

// create the signature
message = bytes(json.dumps(params)).encode('utf-8')
secret = bytes(api_secret).encode('utf-8')
signature = hmac.new(secret, message, digestmod=hashlib.sha256).hexdigest()
	
// add signature to request parameters	
params['signature'] = signature	
Übernimmt man den Python Code einfach so, stellt man fest, dass bytes() und encode() sich beißen. Offenbar machen beide Befehle dasselbe, also einen String in bytes umwandeln. Lässt man entweder bytes() oder encode() weg, dann gibts keine Fehlermeldung mehr und ich denke dann sollte auch alles richtig sein.
Dennoch bekomme ich immer die Meldung, dass die Signatur nicht gültig sei.

Daher mein Gedanke, den PHP Code auszuprobieren und solange rumzuprobieren, bis die signatur aus PHP Code dieselbe ist, wie die aus dem Python Code.
Dabei bin ich dann halt auf das im ersten Post genannte Problem gestoßen, dass es mir nicht gelungen ist, bei exakt denselben Parametern und auch in gleicher Reihenfolge sortiertes Dictionary, eine identische Signatur zu erzeugen (und ja ich hab gemerkt, dass in den Beispiel codes unterschiedliche Parameter verwendet werden ^^)
BlackJack

@Serpens66: Dann musst Du halt schauen wo, nachdem die gleiche Reihenfolge verwendet wird, noch Unterschiede in den serialisierten Daten sind und die beseitigen.

Und die Entwickler der API fragen was sie sich dabei gedacht haben das so zu lösen das man gezwungen ist in verschiedenen Programmiersprachen die JSON-Serialisierung exakt gleich hinzubekommen, wo JSON selbst noch nicht einmal die Reihenfolge in einem JSON-Objekt garantiert und auch andere Faktoren zu unterschiedlichen Serialisierungen für das selbe JSON-Objekt führen können.
Serpens66
User
Beiträge: 259
Registriert: Montag 15. Dezember 2014, 00:31

BlackJack hat geschrieben:@Serpens66: Dann musst Du halt schauen wo, nachdem die gleiche Reihenfolge verwendet wird, noch Unterschiede in den serialisierten Daten sind und die beseitigen.

Und die Entwickler der API fragen was sie sich dabei gedacht haben das so zu lösen das man gezwungen ist in verschiedenen Programmiersprachen die JSON-Serialisierung exakt gleich hinzubekommen, wo JSON selbst noch nicht einmal die Reihenfolge in einem JSON-Objekt garantiert und auch andere Faktoren zu unterschiedlichen Serialisierungen für das selbe JSON-Objekt führen können.
Wenn ich das, was ich als message in das hmac ding reinwerfe printen lasse, dann ist das in Python:
b'[["api_key", "789"], ["nonce", 555]]'
Aber in PHP ist es :
{"api_key":"789","nonce":555}

Habe auch schon versucht, anstelle des json.dumps:
str({"api_key":"789","nonce":555}).encode()
zu nehmen, da das dann etwas näher kommt, aber das kann vermutlich nicht funktionieren, da es dann sogar auf Unterschiede ankommt ob ich normale Anführungszeichen verwende " oder dieses einfache ' ...

Die Entwickler habe ich natürlich schon angeschrieben... vor 4 Wochen und bisher nur die Antwort erhalten, dass sie sich das mal anschauen... Deswegen versuch ich grad nochmal es selbst hinzubekommen... =/
BlackJack

@Serpens66: Du bekommst ziemlich sicher *nicht* b'[["api_key", "789"], ["nonce", 555]]' wenn Du ein Wörterbuch in Python als JSON serialisierst. Da machst Du also ziemlich sicher schon bei den Daten auf Python-Seite etwas falsch/anders als Du solltest. Und wenn bei PHP keine Leerzeichen nach ':' und ',' beim serialisieren als JSON vorkommen: *das* kannst Du in Python mit den entsprechenden Argumenten auch erreichen. Dann noch ein Wörterbuch-Datentyp der wie PHPs assoziative Arrays eine „odered map“ darstellt, und schon sollte das auch in Python klappen. Schau mal ins `collections`-Modul.
Serpens66
User
Beiträge: 259
Registriert: Montag 15. Dezember 2014, 00:31

oh stimmt.. ich hatte das dictionary vorher noch durch ksort gejagt, weil das bei anderen APIs nötig war. Nach ksort hat man sie dort dann mittels urllib.parse.urlencode(params) in einen string verwandelt. Aber sowas steht in unserer aktuellen Doku ja nicht drin, dass man das machen soll...
Also diesen Kram komplett weglassen ^^

Dann mal komplett losgelöster Code.. wobei wir damit dann eigentlich wieder bei meinem ersten Post wären, oder nicht?

Code: Alles auswählen

import json
import hmac,hashlib
params = {"api_key":"789","nonce":555}
message = json.dumps(params).encode()
print(message)
print (hmac.new("123".encode(), message, hashlib.sha256).hexdigest())
es printet:
PS C:\Users\Serpens66\desktop> python test2.py
b'{"api_key": "789", "nonce": 555}'
33009128e8495b0240323f222203af6fb156de51a77dced41ee1ee467257e5f1
PS C:\Users\Serpens66\desktop> python test2.py
b'{"nonce": 555, "api_key": "789"}'
5bfc2958d4de79e3158981f0e687c7799081723618b6ebec8da43ce7b1c08462
Während der vermeintlich identische PHP code:
https://3v4l.org/FM0Mq
{"api_key":"789","nonce":555}
c0a21fbf41349371c297cc78ddcacc9cb7850a21fe70bcd0b7ce7df7789a3519
Habe mir collections und OrderedDict angeschaut.
Aus irgendeinem Grund ändert es dennoch die Reihenfolge, wenn ich
params2 = collections.OrderedDict({"api_key":"789","nonce":555})
mache.
Aber das ist aktuell tatsächlich auch egal, da es bei 2 Einträgen im Dictionary ja nur 2 mögliche Anordnungen gibt. Und wie wir im obigen Code sehen, ist keine der beiden signaturen von PYthon, identisch zu der in PHP.

Bleibt nur die Frage, was du mit folgendem Satz meinst:
Und wenn bei PHP keine Leerzeichen nach ':' und ',' beim serialisieren als JSON vorkommen: *das* kannst Du in Python mit den entsprechenden Argumenten auch erreichen.
Wo ist denn im PHP Code ein ":" und ein "," ?? EDIT: ahcso, habs gesehen ^^ ich schaus mir mal and und editiere dann dazu

Edit2:
Ah jetzt hab ich dasselbe raus :) Lag wirklich an den Leerzeichen, wäre ich nie draf gekommen :D

Code: Alles auswählen

import json
import hmac,hashlib
params = {"api_key":"789","nonce":555}
message = json.dumps(params)
message = message.replace(" ","")
message = message.encode()
print(message)
print (hmac.new("123".encode(), message, hashlib.sha256).hexdigest())
Wie wende ich OrderedDict richtig an, damit einfach die Reihenfolge einbehalten wird, die ich zu anfang vorgebe? In der Doku stehen nur Beispiele mit "sorted", aber was wenn ich keine solche Sortierung will, sondern eine chaotische die ich selbst festlege?

Obwohl wir nun aber in 50% der Fälle (ohne identische Sortierung zur zeit) exakt dasselbe Ergebnis habem wie mit PHP, bekomme ich weiterhin die Fehlermeldung, dass die signatur falsch wäre...
Wir sind zwar einen guten Schritt weiter gekommen ,danke dir dafür! :) Aber mir bleibt wohl trotzdem nichts anderes übrig, als weiterhin auf den support zu warten -.-
BlackJack

@Serpens66: Wenn Du `OrderedDict()` mit einem Dictionary das *keine Reihenfolge* hat fütterst, dann kannst Du es natürlich auch gleich bleiben lassen. Du musst die Schlüssel/Werte schon in der richtigen Reihenfolge übergeben, sonst kann das `OrderedDict` die logischerweise auch nicht kennen und beibehalten. Zum Beispiel als Liste mit Tupeln von Schlüssel/Wert-Paaren.

`replace()` ist der Weg etwas hinterher zu bereinigen was man gar nicht erst haben sollte. Das kann man schon über Optionen beim Serialisieren lösen.
Serpens66
User
Beiträge: 259
Registriert: Montag 15. Dezember 2014, 00:31

Ich danke dir :)

der vollständigkeithalber meine verbesserte Version:

Code: Alles auswählen

json.dumps(params,separators=(',', ':'))

params2 = collections.OrderedDict([["api_key","789"],["nonce",555]])
Antworten