Ivona请求签名问题 - 签名不匹配(AWS签名版本4)

时间:2014-09-27 20:44:42

标签: c# rest amazon-web-services text-to-speech

我正在尝试根据this documnent

实施Ivona请求签名

一切正常,所有结果都与示例值匹配,但签名结果除外。所以签名的结果是cf1141e33a8fbba23913f8f36f29faa524a57db37690a1b819f43bbeaabf3b76,但在文件中它等于2cdfef28d5c5f6682280600a6141a8940c608cfefacb47f172329cbadb5864cc

这是我在Ivona文件中的错误还是错误?

以下是我正在使用的C#代码:

class Program
{
    static void Main()
    {
        try
        {
            Console.WriteLine(SendIvonaRequest());
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }

    private static string SendIvonaRequest()
    {
        var date = new DateTime(2013, 09, 13, 09, 20, 54, DateTimeKind.Utc);

        const string algorithm = "AWS4-HMAC-SHA256";
        const string regionName = "eu-west-1";
        const string serviceName = "tts";
        const string method = "POST";
        const string canonicalUri = "/CreateSpeech";
        const string canonicalQueryString = "";
        const string contentType = "application/json";
        const string accessKey = "MyAccessKey";
        const string secretKey = "MySecretKey";

        const string host = serviceName + "." + regionName + ".ivonacloud.com";

        const string requestPayload = "{\"Input\":{\"Data\":\"Hello world\"}}";

        var hashedRequestPayload = HexEncode(Hash(ToBytes(requestPayload)));

        Debug.Assert(hashedRequestPayload.Equals("f43e25253839f2c3feae433c5e477d79f7dfafdc0e4af19a952adb44a60265ba"));

        var dateStamp = date.ToString("yyyyMMdd");
        var requestDate = date.ToString("yyyyMMddTHHmmss") + "Z";
        var credentialScope = string.Format("{0}/{1}/{2}/aws4_request", dateStamp, regionName, serviceName);

        var headers = new SortedDictionary<string, string>
        {
            {"content-type", "application/json"},
            {"host", "tts.eu-west-1.ivonacloud.com"},
            {"x-amz-content-sha256", hashedRequestPayload},
            {"x-amz-date", requestDate}
        };

        string canonicalHeaders =
            string.Join("\n", headers.Select(x => x.Key.ToLowerInvariant() + ":" + x.Value.Trim())) + "\n";
        const string signedHeaders = "content-type;host;x-amz-content-sha256;x-amz-date";

        // Task 1: Create a Canonical Request For Signature Version 4

        var canonicalRequest = method + '\n' + canonicalUri + '\n' + canonicalQueryString +
                               '\n' + canonicalHeaders + '\n' + signedHeaders + '\n' + hashedRequestPayload;

        var hashedCanonicalRequest = HexEncode(Hash(ToBytes(canonicalRequest)));

        Debug.Assert(hashedCanonicalRequest.Equals("73ff17c0bf9da707afb02bbceb77d359ab945a460b5ac9fff7a0a61cfaab95e6"));

        // Task 2: Create a String to Sign for Signature Version 4
        // StringToSign  = Algorithm + '\n' + RequestDate + '\n' + CredentialScope + '\n' + HashedCanonicalRequest

        var stringToSign = string.Format("{0}\n{1}\n{2}\n{3}", algorithm, requestDate, credentialScope,
            hashedCanonicalRequest);

        Debug.Assert(stringToSign.Equals("AWS4-HMAC-SHA256" + "\n" +
                                         "20130913T092054Z" + "\n" +
                                         "20130913/eu-west-1/tts/aws4_request" + "\n" +
                                         "73ff17c0bf9da707afb02bbceb77d359ab945a460b5ac9fff7a0a61cfaab95e6"));

        // Task 3: Calculate the AWS Signature Version 4

        // HMAC(HMAC(HMAC(HMAC("AWS4" + kSecret,"20130913"),"eu-west-1"),"tts"),"aws4_request")
        byte[] signingKey = GetSignatureKey(secretKey, dateStamp, regionName, serviceName);

        // signature = HexEncode(HMAC(derived-signing-key, string-to-sign))
        var signature = HexEncode(HmacSha256(stringToSign, signingKey));

        Debug.Assert(signature.Equals("2cdfef28d5c5f6682280600a6141a8940c608cfefacb47f172329cbadb5864cc"));

        // Task 4: Prepare a signed request
        // Authorization: algorithm Credential=access key ID/credential scope, SignedHeadaers=SignedHeaders, Signature=signature

        var authorization =
            string.Format("{0} Credential={1}/{2}/{3}/{4}/aws4_request, SignedHeaders={5}, Signature={6}",
                algorithm, accessKey, dateStamp, regionName, serviceName, signedHeaders, signature);

        // Send the request

        var webRequest = WebRequest.Create("https://" + host + canonicalUri);

        webRequest.Method = method;
        webRequest.Timeout = 2000;
        webRequest.ContentType = contentType;
        webRequest.Headers.Add("X-Amz-date", requestDate);
        webRequest.Headers.Add("Authorization", authorization);
        webRequest.Headers.Add("x-amz-content-sha256", hashedRequestPayload);
        webRequest.ContentLength = requestPayload.Length;

        using (Stream newStream = webRequest.GetRequestStream())
        {
            newStream.Write(ToBytes(requestPayload), 0, requestPayload.Length);
        }

        var response = (HttpWebResponse) webRequest.GetResponse();

        using (Stream responseStream = response.GetResponseStream())
        {
            if (responseStream != null)
            {
                using (var streamReader = new StreamReader(responseStream))
                {
                    return streamReader.ReadToEnd();
                }
            }
        }

        return string.Empty;
    }

    private static byte[] GetSignatureKey(String key, String dateStamp, String regionName, String serviceName)
    {
        byte[] kDate = HmacSha256(dateStamp, ToBytes("AWS4" + key));
        byte[] kRegion = HmacSha256(regionName, kDate);
        byte[] kService = HmacSha256(serviceName, kRegion);
        return HmacSha256("aws4_request", kService);
    }

    private static byte[] ToBytes(string str)
    {
        return Encoding.UTF8.GetBytes(str.ToCharArray());
    }

    private static string HexEncode(byte[] bytes)
    {
        return BitConverter.ToString(bytes).Replace("-", string.Empty).ToLowerInvariant();
    }

    private static byte[] Hash(byte[] bytes)
    {
        var sha256 = SHA256.Create();
        return sha256.ComputeHash(bytes);
    }

    private static byte[] HmacSha256(String data, byte[] key)
    {
        return new HMACSHA256(key).ComputeHash(ToBytes(data));
    }
}

UPD1:

我还尝试了http://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html中的示例,并注意到我的代码生成了与这些示例中相同的签名。所以我假设Ivona文件中存在问题...

UPD2:

一切正常!我已根据the description实现了CreateSpeech方法,并将使用的完整示例上传到GitHub https://github.com/MalyutinS/DotNetIvonaAPI

2 个答案:

答案 0 :(得分:0)

解决!实际上文档示例中存在问题。所以代码工作正常。

答案 1 :(得分:0)

现在有点老了,但使用此代码可能与其他人有关;

只要你只坚持使用ASCII字符,代码就可以正常工作。对于英语以外的语言,必须先将字符串转换为UTF-8字节数组。该数组可能比字符串中的字符数长,因此通过WebRequest发送的字节数组的长度必须反映数组本身的长度,而不是字符数。

真的很经典,但无论如何都要指出..

此错误导致签名不正确,因此出现身份验证错误。原因可能不是每个人都明白的。

现有代码;

    webRequest.Method = method;
    webRequest.Timeout = 2000;
    webRequest.ContentType = contentType;
    webRequest.Headers.Add("X-Amz-date", requestDate);
    webRequest.Headers.Add("Authorization", authorization);
    webRequest.Headers.Add("x-amz-content-sha256", hashedRequestPayload);
    webRequest.ContentLength = requestPayload.Length;

    using (Stream newStream = webRequest.GetRequestStream())
    {
        newStream.Write(ToBytes(requestPayload), 0, requestPayload.Length);
    }

<强>变为:

    var bytes = ToBytes(requestPayload);
    webRequest.Method = method;
    webRequest.Timeout = 2000;
    webRequest.ContentType = contentType;
    webRequest.Headers.Add("X-Amz-date", requestDate);
    webRequest.Headers.Add("Authorization", authorization);
    webRequest.Headers.Add("x-amz-content-sha256", hashedRequestPayload);
    webRequest.ContentLength = bytes.Length;

    using (Stream newStream = webRequest.GetRequestStream())
    {
        newStream.Write(bytes, 0, bytes.Length);
        newStream.Flush();
    }

也;有关执行AWS-4签名的更合适和可重用的方法,请参阅亚马逊拥有:http://aws.amazon.com/code