Ich muss eine Signatur für Amazon MWS generieren und entschied mich, eine Lösung nur mit den Komponenten und Klassen zu finden, die mit Delphi kommen. Da ich Indy für den HTTP-Post selbst verwende, schien es eine gute Idee zu sein, Indy-Klassen für die Berechnung des RFC 2104-kompatiblen HMAC zu verwenden.Erstellen einer Amazon MWS-Signatur mit Delphi XE7 und Indy-Klassen
Für andere, die auf Amazon Integration annehmen, die Schaffung der „canonicalized Abfrage String“ ist sehr gut in den Amazon-Tutorial erklärt: http://docs.developer.amazonservices.com/en_DE/dev_guide/DG_ClientLibraries.html Seien Sie vorsichtig, gerade # 10 für Zeilenumbruch verwenden, wie # 13 # 10 oder # 13 wird mit einer falschen Signatur fehlschlagen. Es kann auch wichtig sein, "amazon Endpoint" (Host) ": 443" hinzuzufügen, abhängig von der TIdHttp-Version, wie in Frage Nr. 23573799 erklärt.
Um eine gültige Signatur zu erstellen, müssen wir einen HMAC mit SHA256 mit der Abfragezeichenfolge und dem SecretKey berechnen, den wir von Amazon nach der Registrierung erhalten haben, und dann muss das Ergebnis in BASE64 kodiert werden.
Die Abfragezeichenfolge wird ordnungsgemäß generiert und ist identisch mit der Zeichenfolge, die das Amazon Scratchpad erstellt. Aber der Anruf ist fehlgeschlagen, weil die Signatur nicht korrekt ist.
Nach einigen Tests stellte ich fest, dass die Signatur, die ich von meiner Abfragezeichenkette erhielt, nicht das selbe ist, das ich erhielt, als ich PHP verwendete, um es zu erzeugen. Das PHP-Ergebnis wird als korrekt angesehen, da meine PHP-Lösung seit langem mit Amazon funktioniert, das Delphi-Ergebnis ist anders, was nicht stimmt.
Um das Testen zu erleichtern, verwende ich '1234567890' als Wert für die Abfragezeichenfolge und 'ABCDEFG' als Ersatz für den SecretKey. Wenn das Ergebnis, das ich mit Delphi erhalte, dasselbe ist wie das Ergebnis, das ich mit PHP erhalte, sollte das Problem gelöst sein, glaube ich.
Hier ist, wie ich das richtige Ergebnis mit PHP erhalten:
echo base64_encode(hash_hmac('sha256', '1234567890', 'ABCDEFG', TRUE));
Dies zeigt ein Ergebnis der
aRGlc3RY1pKmKX0hvorkVKNcPigiJX2rksqXzlAeCLg=
Der folgende Delphi XE7 Code gibt das falsche Ergebnis, während der indy-Version verwenden, kommt mit Delphi XE7:
uses
IdHash, IdHashSHA, IdHMACSHA1, IdSSLOpenSSL, IdGlobal, IdCoderMIME;
function GenerateSignature(const AData, AKey: string): string;
var
AHMAC: TIdBytes;
begin
IdSSLOpenSSL.LoadOpenSSLLibrary;
With TIdHMACSHA256.Create do
try
Key:= ToBytes(AKey, IndyTextEncoding_UTF16LE);
AHMAC:= HashValue(ToBytes(AData, IndyTextEncoding_UTF16LE));
Result:= TIdEncoderMIME.EncodeBytes(AHMAC);
finally
Free;
end;
end;
Hier das Ergebnis, das in einem Memo mit angezeigt wird
Memo.Lines.Text:= GenerateSignature('1234567890', 'ABCDEFG');
ist:
jg6Oddxvv57fFdcCPXrqGWB9YD5rSvtmGnZWL0X+y0Y=
Ich glaube, das Problem etwas mit den Kodierungen zu tun hat, so habe ich einige der Forschung rund um das getan. Wie das Amazon Tutorial (Link siehe oben) sagt, erwartet Amazon eine utf8 Kodierung.
Da die Indy-Funktion "ToBytes" einen String erwartet, der in meiner Delphi-Version ein UnicodeString ist, beende ich das Testen mit anderen String-Typen als UTF8String für Parameter oder Variablen, aber ich weiß einfach nicht, wo utf8 in Position kommen soll . Ich weiß auch nicht, ob die Kodierungen, die ich im obigen Code verwende, die richtigen sind. Ich wähle UTF16LE, weil UnicodeString utf16-kodiert ist (Details siehe http://docwiki.embarcadero.com/RADStudio/Seattle/en/String_Types_(Delphi)) und LE (Little-Endian) wird am häufigsten auf modernen Rechnern verwendet. Auch die TEncodings von Delphi selbst gibt es "Unicode" und "BigEndianUnicode", so dass "Unicode" LE und eine Art "Standard" Unicode zu sein scheint. Natürlich habe ich getestet IndyTextEncoding_UTF8 anstelle von IndyTextEncoding_UTF16LE im obigen Code zu verwenden, aber es funktioniert sowieso nicht.
Da
TIdEncoderMIME.EncodeBytes(AHMAC);
ist das TidBytes zu einem Strom zuerst zu schreiben und dann alles mit 8-Bit-Codierung zu lesen, könnte dies auch eine Quelle des Problems, so dass ich auch getestet mit
Result:= BytesToString(AHMAC, IndyTextEncoding_UTF16LE);
Result:= TIdEncoderMIME.EncodeString(Result, IndyTextEncoding_UTF16LE);
aber das Ergebnis ist das gleiche.
Wenn Sie das Haupt-Code zu sehen, die Anforderung für die Erstellung, hier ist es:
function TgboAmazon.MwsRequest(const AFolder, AVersion: string;
const AParams: TStringList; const AEndPoint: string): string;
var
i: Integer;
SL: TStringList;
AMethod, AHost, AURI, ARequest, AStrToSign, APath, ASignature: string;
AKey, AValue, AQuery: string;
AHTTP: TIdHTTP;
AStream, AResultStream: TStringStream;
begin
AMethod:= 'POST';
AHost:= AEndPoint;
AURI:= '/' + AFolder + '/' + AVersion;
AQuery:= '';
SL:= TStringList.Create;
try
SL.Assign(AParams);
SL.Values['AWSAccessKeyId']:= FAWSAccessKeyId;
SL.Values['SellerId']:= FSellerId;
FOR i:=0 TO FMarketplaceIds.Count-1 DO
begin
SL.Values['MarketplaceId.Id.' + IntToStr(i+1)]:= FMarketplaceIds[i];
end;
SL.Values['Timestamp']:= GenerateTimeStamp(Now);
SL.Values['SignatureMethod']:= 'HmacSHA256';
SL.Values['SignatureVersion']:= '2';
SL.Values['Version']:= AVersion;
FOR i:=0 TO SL.Count-1 DO
begin
AKey:= UrlEncode(SL.Names[i]);
AValue:= UrlEncode(SL.ValueFromIndex[i]);
SL[i]:= AKey + '=' + AValue;
end;
SortList(SL);
SL.Delimiter:= '&';
AQuery:= SL.DelimitedText;
AStrToSign:= AMethod + #10 + AHost + #10 + AURI + #10 + AQuery;
TgboUtil.ShowMessage(AStrToSign);
ASignature:= GenerateSignature(AStrToSign, FAWSSecretKey);
TgboUtil.ShowMessage(ASignature);
APath:= 'https://' + AHost + AURI + '?' + AQuery + '&Signature=' + Urlencode(ASignature);
TgboUtil.ShowMessage(APath);
finally
SL.Free;
end;
AHTTP:= TIdHTTP.Create(nil);
try
AHTTP.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(AHTTP);
AHTTP.Request.ContentType:= 'text/xml';
AHTTP.Request.Connection:= 'Close';
AHTTP.Request.CustomHeaders.Add('x-amazon-user-agent: MyApp/1.0 (Language=Delphi/XE7)');
AHTTP.HTTPOptions:= AHTTP.HTTPOptions + [hoKeepOrigProtocol];
AHTTP.ProtocolVersion:= pv1_0;
AStream:= TStringStream.Create;
AResultStream:= TStringStream.Create;
try
AHTTP.Post(APath, AStream, AResultStream);
Result:= AResultStream.DataString;
ShowMessage(Result);
finally
AStream.Free;
AResultStream.Free;
end;
finally
AHTTP.Free;
end;
end;
urlencode und GenerateTimestamp sind meine eigenen Funktionen und sie tun, was der Name verspricht, SortList mein eigenes Verfahren, das sortiert Die Stringliste in einer Byte-Reihenfolge, wie von Amazon angefordert, TgboUtil.ShowMessage ist meine eigene ShowMessage-Alternative, die die vollständige Nachricht mit allen Zeichen zeigt und nur zum Debuggen verwendet wird. Das HTTP-Protokoll ist 1.0 nur zum Testen, weil ich eine 403 (Berechtigung verweigert) als HTTP-Rückgabe früher erhalten habe. Ich wollte das nur als Problem ausschließen, wie die Indy-Dokumentation besagt, dass die Protokollversion 1.1 wegen problematischer Serverantworten als unvollständig angesehen wird.
Es gibt mehrere Beiträge in Bezug auf die amazon mws Thema hier, aber dieses spezifische Problem scheint neu zu sein.
Diese Frage hier kann jemandem helfen, der gerade nicht so weit gekommen ist, aber ich hoffe auch, dass jemand eine Lösung anbieten kann, um den gleichen Signaturwert in Delphi zu erhalten, wie ich es mit PHP bekommen habe.
Vielen Dank im Voraus.
Bitte beachten Sie die Antwort von Remy Lebeau für einige gute Änderungen meines Codes erklärt, so wird es funktionieren in das Ende. Damit interessierte Besucher das Problem und seine Lösung besser verstehen können, habe ich beschlossen, den Code unbearbeitet zu lassen, mit Ausnahme des Lecks von 'TIdSSLIOHandlerSocketOpenSSL' – KaiW