0

阅读HTTP API 文档。我的请求因签名错误而失败。从错误消息中,我可以看到我要签名的字符串是正确的,但看起来我无法生成正确的 HMAC-SHA1(说真的为什么还要使用 SHA1 呢??)。

所以我决定尝试在同一个文档中复制样本的签名。

[47] pry(main)> to_sign = "GET&%2F&AccessKeyId%3Dtestid&Action%3DDescribeRegions&Format%3DXML&SignatureMethod%3DHMAC-SHA1&SignatureNonce%3D3ee8c1b8-83d3-44af-a94f-4e0ad82fd6cf&SignatureVersion%3D1.0&Timestamp%3D2016-02-23T12%253A46%253A24Z&Version%3D2014-05-26"

[48] pry(main)> Base64.encode64 OpenSSL::HMAC.digest("sha1", "testsecret", to_sign)
=> "MLAxpXej4jJ7TL0smgWpOgynR7s=\n"

[49] pry(main)> Base64.encode64 OpenSSL::HMAC.digest("sha1", "testsecret&", to_sign)
=> "VyBL52idtt+oImX0NZC+2ngk15Q=\n"

[50] pry(main)> Base64.encode64 OpenSSL::HMAC.hexdigest("sha1", "testsecret&", to_sign)
=> "NTcyMDRiZTc2ODlkYjZkZmE4MjI2NWY0MzU5MGJlZGE3ODI0ZDc5NA==\n"

[51] pry(main)> Base64.encode64 OpenSSL::HMAC.hexdigest("sha1", "testsecret", to_sign)
=> "MzBiMDMxYTU3N2EzZTIzMjdiNGNiZDJjOWEwNWE5M2EwY2E3NDdiYg==\n"

[52] pry(main)> OpenSSL::HMAC.hexdigest("sha1", "testsecret&", to_sign)
=> "57204be7689db6dfa82265f43590beda7824d794"

[53] pry(main)> OpenSSL::HMAC.hexdigest("sha1", "testsecret", to_sign)
=> "30b031a577a3e2327b4cbd2c9a05a93a0ca747bb"

很明显,这些都不匹配的示例签名CT9X0VtwR86fNWSnsc6v8YGOjuE=。知道这里缺少什么吗?

更新:tcpdumpGolang 客户端工具中,我看到它执行POST如下请求:

POST /?AccessKeyId=**********&Action=DescribeRegions&Format=JSON&RegionId=cn-qingdao&Signature=aHZVpIMb0%2BFKdoWSIVaFJ7bd2LA%3D&SignatureMethod=HMAC-SHA1&SignatureNonce=c29a0e28964c470a8997aebca4848b57&SignatureType=&SignatureVersion=1.0&Timestamp=2018-07-16T19%3A46%3A33Z&Version=2014-05-26 HTTP/1.1

    Host: ecs.aliyuncs.com
    User-Agent: Aliyun-CLI-V3.0.3
    Content-Length: 0
    Content-Type: application/x-www-form-urlencoded
    x-sdk-client: golang/1.0.0
    x-sdk-core-version: 0.0.1
    x-sdk-invoke-type: common
    Accept-Encoding: gzip

当我从上述请求中获取参数并生成签名时,它确实匹配。所以我尝试了所有 tree: GETPOST带有 URL 参数和POST正文中的参数。每次我收到签名错误。如果我使用与 golang 工具完全相同的参数重做请求,我会收到 nonce already used 错误(如预期的那样)。

4

2 回答 2

1
  • 看来这个aliyun ruby​​ sdk(非官方,仅供参考)的作品。您可能想检查它是如何实现的
  • 检查它的 string_to_sign 的样子。我跑了一下,似乎与您提供的略有不同。参数与&而不是连接%26GET&%2F&AccessKeyId%3Dtestid&Action%3DDescribeRegions&Format%3DXML&SignatureMethod%3DHMAC-SHA1&SignatureNonce%3D3ee8c1b8-83d3-44af-a94f-4e0ad82fd6cf&SignatureVersion%3D1.0&Timestamp%3D2016-02-23T12%253A46%253A24Z&Version%3D2014-05-26
    需要“红宝石”
    需要“阿里云”

    $DEBUG = 真

    选项 = {
      :access_key_id => "k",
      :access_key_secret => "s",
      :service => :ecs
    }

    service = Aliyun::Service.new 选项

    将 service.DescribeRegions({})
于 2018-07-13T21:33:06.207 回答
1

终于得到了这个工作。就我而言,主要问题是我一直在对签名参数进行双百分比编码,因此结果无效。对我帮助最大的是运行aliyuncli 实用程序并捕获流量,然后运行具有完全相同参数的查询来比较确切的查询字符串。

但让我为我列出一些要点:

  1. 一旦生成了 hmac-sha1 sig,不要对其进行百分比编码,只需将其添加到具有普通形式 www 编码的查询中
  2. HTTP 查询中的参数顺序不重要;签名字符串中的参数顺序重要
  3. 我发现以下所有类型的请求都可以工作:GET,带有 URL 查询参数的 POST,带有 www 编码的请求正文中的参数的 POST;我在每个文档中使用 GET 但我看到aliyun在查询中使用 POST vs 查询参数和有序参数
  4. &生成 HMAC-SHA1 时必须在密钥末​​尾添加字符
  5. 以二进制形式生成 HMAC-SHA1,然后编码为 Base64(无十六进制值)
  6. 某些参数可能不区分大小写,例如Format同时用作jsonJSON
  7. 我看到aliyun, @wanghq 和John使用 UUID 4 作为 SignatureNonce 但我推迟到纯随机(根据文档),因为它似乎只是一个重放攻击保护。所以加密安全的随机数必须是不必要的。
  8. 和的特殊编码规则似乎只适用于用于签名的字符串,实际上并不适用于在 HTTP 查询中以这种方式编码数据+*~

我决定不使用@wanghq 的包装器,因为它对我不起作用,也禁用了证书验证,但也许它会被修复。只是我认为一旦确定了签名,查询就足够简单了,而且额外的间接层是不值得的。尽管对他的回答+1,因为这有助于让我的签名正确。

这是发出简单请求的示例 ruby​​ 代码:

require 'base64'
require 'cgi'
require 'openssl'
require 'time'
require 'rest-client'

# perform a request against Alibaba Cloud API
# @see https://www.alibabacloud.com/help/doc-detail/25489.htm
def request(action:, params: {})
  api_url = "https://ecs.aliyuncs.com/"

  # method = "POST"
  method = "GET"
  process_params!(http: method, action: action, params: params)
  RestClient::Request.new(method: method, url: api_url, headers: {params: params})
  # RestClient::Request.new(method: method, url: api_url, payload: params)
  # RestClient::Request.new(method: method, url: api_url, payload: params.map{|k,v| "#{k}=#{CGI.escape(v)}"}.join("&"))
end

# generates the required common params for a request and adds them to params
# @return undefined
# @see https://www.alibabacloud.com/help/doc-detail/25490.htm
def process_params!(http:, action:, params:)
  params.merge!({
    "Action" => action,
    "AccessKeyId" => config[:auth][:key_id],
    "Format" => "JSON",
    "Version" => "2014-05-26",
    "Timestamp" => Time.now.utc.iso8601
  })
  sign!(http: http, action: action, params: params)
end

# generate request signature and adds to params
# @return undefined
# @see https://www.alibabacloud.com/help/doc-detail/25492.htm
def sign!(http:, action:, params:)
  params.delete "Signature"
  params["SignatureMethod"] = "HMAC-SHA1"
  params["SignatureVersion"] = "1.0"
  params["SignatureNonce"] = "#{rand(1_000_000_000_000)}"
  # params["SignatureNonce"] = SecureRandom.uuid.gsub("-", "")

  canonicalized_query_string = params.sort.map { |key, value|
    "#{key}=#{percent_encode value}"
  }.join("&")

  string_to_sign = %{#{http}&#{percent_encode("/")}&#{percent_encode(canonicalized_query_string)}}

  params["Signature"] = hmac_sha1(string_to_sign)
end

# @param data [String]
# @return [String]
def hmac_sha1(data, secret: config[:auth][:key_secret])
  Base64.encode64(OpenSSL::HMAC.digest('sha1', "#{secret}&", data)).strip
end

# encode strings per Alibaba cloud rules for signing
# @return [String] encoded string
# @see https://www.alibabacloud.com/help/doc-detail/25492.htm
def percent_encode(str)
  CGI.escape(str).gsub(?+, "%20").gsub(?*, "%2A").gsub("%7E", ?~)
end

## example call
request(action: "DescribeRegions")

代码可以稍微简化一点,但决定让它非常接近文档说明。

PS不知道为什么约翰删除了他的答案,但在上面留下了一个指向他的网页的链接,供任何寻找示例代码的python人使用

于 2018-07-17T14:20:18.140 回答