gpt4 book ai didi

delphi - 使用Delphi XE7和Indy类创建亚马逊MWS签名

转载 作者:行者123 更新时间:2023-12-03 15:53:55 26 4
gpt4 key购买 nike

我需要为亚马逊MWS生成一个签名,并决定找到仅包含Delphi随附的组件和类的解决方案。因为我将Indy用于HTTP帖子本身,所以使用Indy类来计算符合RFC 2104的HMAC似乎是一个好主意。

对于从事亚马逊集成工作的其他人,在亚马逊教程中很好地解释了“规范化查询字符串”的创建:http://docs.developer.amazonservices.com/en_DE/dev_guide/DG_ClientLibraries.html
请注意,只需使用#10换行,因为#13#10或#13会因错误的签名而失败。根据问题TIdHttp的版本,将“:443”添加到亚马逊端点(主机)也可能很重要,如问题#23573799所述。

为了创建有效的签名,我们必须使用SHA256来计算HMAC,并使用查询字符串和注册后从亚马逊获得的SecretKey进行查询,然后将结果编码为BASE64。

查询字符串已正确生成,并且与Amazon Scratchpad创建的字符串相同。但是呼叫失败,因为签名不正确。

经过一些测试,我意识到从查询字符串中获得的签名与使用PHP生成签名时得到的结果不同。 PHP结果被认为是正确的,因为很长一段时间以来,我的PHP解决方案仅可与Amazon一起使用,因此Delphi结果是不同的,这是不正确的。

为了简化测试,我使用“ 1234567890”作为查询字符串的值,并使用“ ABCDEFG”代替SecretKey。我相信,当我用Delphi得到的结果与我用PHP得到的结果相同时,应该解决问题。

这是我如何通过PHP获得正确的结果:

echo base64_encode(hash_hmac('sha256', '1234567890', 'ABCDEFG', TRUE));


这显示了

aRGlc3RY1pKmKX0hvorkVKNcPigiJX2rksqXzlAeCLg=


在使用Delphi XE7随附的Indy版本时,以下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;


此处的结果在备忘录中显示为

Memo.Lines.Text:= GenerateSignature('1234567890', 'ABCDEFG'); 


是:

jg6Oddxvv57fFdcCPXrqGWB9YD5rSvtmGnZWL0X+y0Y=


我认为问题与编码有关,因此我对此进行了一些研究。正如amazon教程(见上面的链接)所讲的那样,amazon希望使用utf8编码。

由于Indy函数“ ToBytes”期望一个字符串(在我的Delphi版本中为UnicodeString),我退出了对其他字符串类型的测试,如参数或变量的UTF8String,但是我只是不知道utf8应该放在哪里。我也不知道我在上面的代码中使用的编码是否正确。
我选择UTF16LE是因为UnicodeString是utf16编码的(有关详细信息,请参见 http://docwiki.embarcadero.com/RADStudio/Seattle/en/String_Types_(Delphi)),而LE(小尾数)是现代计算机上最常用的。另外,Delphi本身的TEncodings也有“ Unicode”和“ BigEndianUnicode”,因此“ Unicode”似乎是LE和某种“标准” Unicode。
当然,我测试了在上面的代码中使用IndyTextEncoding_UTF8而不是IndyTextEncoding_UTF16LE,但是它仍然无法正常工作。

因为

TIdEncoderMIME.EncodeBytes(AHMAC);


首先将TidBytes写入Stream,然后使用8bit编码读取所有内容,这也可能是问题的根源,因此我也进行了测试

Result:= BytesToString(AHMAC, IndyTextEncoding_UTF16LE);
Result:= TIdEncoderMIME.EncodeString(Result, IndyTextEncoding_UTF16LE);


但结果是一样的。

如果您想查看用于创建请求的主要代码,则为:

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和GenerateTimestamp是我自己的函数,它们按照名称的承诺执行操作,SortList是我自己的过程,该过程按亚马逊要求的字节顺序对字符串列表进行排序,TgboUtil.ShowMessage是我自己的ShowMessage替代方案,它显示包含所有字符的完整消息,以及仅用于调试。 http协议仅是用于测试的1.0,因为较早的HTTP返回时,我得到了403(权限被拒绝)。正如Indy文档所说,我只是想将其排除在问题之外,因为服务器问题的存在,协议版本1.1被认为是不完整的。

这里有关于亚马逊MWS主题的几篇文章,但是这个特定问题似乎是新的。

这里的问题可能会帮助到现在为止还没有走过的人,但是我也希望有人可以提供一种解决方案,以使Delphi中的签名值与PHP相同。

先感谢您。

最佳答案

使用Indy 10的最新SVN快照,我无法重现您的签名问题。使用UTF-8时,示例键+值数据在Delphi中产生的结果与PHP输出相同。因此,只要满足以下条件,您的GenerateSignature()函数就可以了:


您使用IndyTextEncoding_UTF8而不是IndyTextEncoding_UTF16LE
您确保ADataAKey包含有效的输入数据。


另外,您应确保TIdHashSHA256.IsAvailable返回true,否则TIdHashHMACSHA256.HashValue()将失败。
例如,如果OpenSSL无法加载,则可能会发生这种情况。

尝试以下方法:

function GenerateSignature(const AData, AKey: string): string;
var
AHMAC: TIdBytes;
begin
IdSSLOpenSSL.LoadOpenSSLLibrary;

if not TIdHashSHA256.IsAvailable then
raise Exception.Create('SHA-256 hashing is not available!');

with TIdHMACSHA256.Create do
try
Key := IndyTextEncoding_UTF8.GetBytes(AKey);
AHMAC := HashValue(IndyTextEncoding_UTF8.GetBytes(AData));
finally
Free;
end;

Result := TIdEncoderMIME.EncodeBytes(AHMAC);
end;


话虽如此,您的 MwsRequest()函数存在很多问题:


您正在泄漏 TIdSSLIOHandlerSocketOpenSSL对象。您没有为其分配 Owner,并且 TIdHTTP分配给它的 IOHandler属性时也没有所有权。实际上,在您的示例中分配 IOHanlder实际上是可选的,有关原因,请参见 New HTTPS functionality for TIdHTTP
您将 AHTTP.Request.ContentType设置为错误的媒体类型。您不发送XML数据,因此请勿将媒体类型设置为 'text/xml'。在这种情况下,您需要将其设置为 'application/x-www-form-urlencoded'
调用 AHTTP.Post()时, AStream流为空,因此实际上并没有向服务器发布任何数据。您将 AQuery数据放入URL本身的查询字符串中,但实际上它属于 AStream。如果要发送URL查询字符串中的数据,则必须使用 TIdHTTP.Get()而不是 TIdHTTP.Post(),并将 AMethod值更改为 'GET'而不是 'POST'
您正在使用 TIdHTTP.Post()的版本填充输出 TStream。您正在使用 TStringStream将响应转换为 String,而不考虑响应数据使用的实际字符集。由于您没有在 TEncoding构造函数中指定任何 TStringStream对象,因此它将使用 TEncoding.Default进行解码,这可能与(可能不会)与响应的实际字符集匹配。您应该改用返回 Post()的其他版本的 String,以便 TIdHTTP可以根据HTTPS服务器报告的实际字符集对响应数据进行解码。


尝试类似这样的方法:

function TgboAmazon.MwsRequest(const AFolder, AVersion: string;
const AParams: TStringList; const AEndPoint: string): string;
var
i: Integer;
SL: TStringList;
AMethod, AHost, AURI, AQuery, AStrToSign, APath, ASignature: string;
AHTTP: TIdHTTP;
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;
SL.Values['Signature'] := '';

SortList(SL);

for i := 0 to SL.Count-1 do
SL[i] := UrlEncode(SL.Names[i]) + '=' + UrlEncode(SL.ValueFromIndex[i]);

SL.Delimiter := '&';
SL.QuoteChar := #0;
SL.StrictDelimiter := True;
AQuery := SL.DelimitedText;
finally
SL.Free;
end;

AStrToSign := AMethod + #10 + Lowercase(AHost) + #10 + AURI + #10 + AQuery;
TgboUtil.ShowMessage(AStrToSign);

ASignature := GenerateSignature(AStrToSign, FAWSSecretKey);
TgboUtil.ShowMessage(ASignature);

APath := 'https://' + AHost + AURI;
TgboUtil.ShowMessage(APath);

AHTTP := TIdHTTP.Create(nil);
try
// this is actually optional in this example...
AHTTP.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(AHTTP);

AHTTP.Request.ContentType := 'application/x-www-form-urlencoded';
AHTTP.Request.Connection := 'close';
AHTTP.Request.UserAgent := 'MyApp/1.0 (Language=Delphi/XE7)';
AHTTP.Request.CustomHeaders.Values['x-amazon-user-agent'] := 'MyApp/1.0 (Language=Delphi/XE7)';
AHTTP.HTTPOptions := AHTTP.HTTPOptions + [hoKeepOrigProtocol];
AHTTP.ProtocolVersion := pv1_0;

AStream := TStringStream.Create(AQuery + '&Signature=' + Urlencode(ASignature);
try
Result := AHTTP.Post(APath, AStream);
ShowMessage(Result);
finally
AStream.Free;
end;
finally
AHTTP.Free;
end;
end;


但是,由于将响应记录为XML,因此最好将响应以 TStream(虽然不使用 TStringStream)或 TBytes而不是 String的形式返回给调用方。这样,让XML解析器自己解码原始字节,而不是Indy解码字节。 XML具有与HTTP分开的自己的字符集规则,因此让XML解析器为您完成其工作:

procedure TgboAmazon.MwsRequest(...; Response: TStream);
var
...
begin
...
AHTTP.Post(APath, AStream, Response);
...
end;

关于delphi - 使用Delphi XE7和Indy类创建亚马逊MWS签名,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40088519/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com