我需要为亚马逊MWS生成签名,并决定仅使用Delphi附带的组件和类来查找解决方案。因为我使用Indy作为自己的HTTP帖子,所以使用Indy类来计算RFC 2104兼容的HMAC似乎是个好主意。使用Delphi XE7和Indy类创建亚马逊MWS签名
对于其他人,谁在亚马逊整合工作,“规范化查询字符串”的创作在亚马逊教程是很好的解释:http://docs.developer.amazonservices.com/en_DE/dev_guide/DG_ClientLibraries.html 小心,只使用断行#10,如#13#10或#13将失败,并签名错误。如问题#23573799所述,根据TIdHttp版本添加“:443”到亚马逊端点(主机)也很重要。
要创建一个有效的签名,我们必须使用SHA256计算一个HMAC,其中包含查询字符串和我们从注册后从亚马逊获得的SecretKey,然后结果必须在BASE64中编码。
正确生成查询字符串,并且与亚马逊Scratchpad创建的字符串相同。但由于签名不正确,通话失败。
经过一些测试后,我意识到从我的查询字符串中得到的签名与我在使用PHP生成时的结果不一样。 PHP的结果被认为是正确的,因为我的PHP解决方案很长一段时间与亚马逊一起工作,德尔菲的结果是不同的,这是不正确的。
为了使测试更容易,我使用'1234567890'作为查询字符串的值,'ABCDEFG'作为SecretKey的替换值。当我使用Delphi获得的结果与使用PHP获得的结果相同时,我相信问题应该得到解决。
这是我如何得到正确的结果与PHP:
echo base64_encode(hash_hmac('sha256', '1234567890', 'ABCDEFG', TRUE));
这显示
aRGlc3RY1pKmKX0hvorkVKNcPigiJX2rksqXzlAeCLg=
以下德尔福XE7代码返回错误结果的结果,在使用印版本附带德尔福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
Memo.Lines.Text:= GenerateSignature('1234567890', 'ABCDEFG');
是:
jg6Oddxvv57fFdcCPXrqGWB9YD5rSvtmGnZWL0X+y0Y=
我相信这个问题有什么做的编码,所以我做了周围的一些研究。正如亚马逊教程(链接参见上文)所述,亚马逊期望采用utf8编码。
由于Indy函数“ToBytes”期望一个字符串,它是我的Delphi版本中的一个UnicodeString,所以我放弃使用其他字符串类型作为参数或变量的UTF8String进行测试,但我不知道utf8应该在何处到位。此外,我不知道我在上面的代码中使用的编码是否正确。 我选择UTF16LE是因为UnicodeString是utf16编码(详见http://docwiki.embarcadero.com/RADStudio/Seattle/en/String_Types_(Delphi)),LE(Little-Endian)是现代机器上最常用的。德尔福的TEncodings本身也有“Unicode”和“BigEndianUnicode”,所以“Unicode”似乎是LE和某种“标准”的Unicode。 当然,我在上面的代码中测试过使用IndyTextEncoding_UTF8而不是IndyTextEncoding_UTF16LE,但它无论如何都不起作用。
由于
TIdEncoderMIME.EncodeBytes(AHMAC);
首先写入TidBytes到Stream,然后用8位编码读取这一切,这可能是问题也是一个来源,所以我还与
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协议仅用于测试,因为我得到了403(拒绝的权限),因为HTTP返回的时间较早。我只是想排除这个问题,因为indy文档说,协议版本1.1被认为是不完整的,因为有问题的服务器答案。
这里有几个关于amazon mws主题的帖子,但是这个具体问题似乎是新的。
这里的这个问题可能会帮助那些目前还没有到达的人,也希望有人可以提供一个解决方案,只需在Delphi中获得与PHP相同的签名值。
预先感谢您。
请参阅雷米勒博的回答一些好的解释了我的代码更改,所以它会工作,结束。因此,感兴趣的访问者可以更好地理解问题及其解决方案,我决定保留未经编辑的代码,除了'TIdSSLIOHandlerSocketOpenSSL' – KaiW