阿里云生成签名问题
troubles generating signature for alibaba cloud
正在阅读HTTP API docs。我的请求因签名错误而失败。从错误消息中我可以看到我要签名的字符串是正确的,但看起来我无法生成正确的 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"
很明显,其中 none 与 CT9X0VtwR86fNWSnsc6v8YGOjuE=
的示例签名匹配。知道这里缺少什么吗?
更新: 从 Golang 客户端工具获取 tcpdump
我看到它做了一个 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
当我从上述请求中获取参数并生成签名时,它 匹配。所以我尝试了所有树:GET
、POST
和 URL 参数以及 POST
和正文中的参数。每次我收到签名错误。如果我使用与 golang 工具完全相同的参数重做请求,我会收到 nonce already used 错误(如预期的那样)。
- 好像是这个aliyun ruby sdk (non official, just for reference) works. You may want to check how it's implemented.
- 检查其 string_to_sign 的外观。我做了一个 运行,它似乎与您提供的略有不同。参数与
&
而不是 %26
连接。
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
require 'rubygems'
require 'aliyun'
$DEBUG = true
options = {
:access_key_id => "k",
:access_key_secret => "s",
:service => :ecs
}
service = Aliyun::Service.new options
puts service.DescribeRegions({})
终于成功了。在我的案例中,主要问题是我一直在对签名参数进行双百分比编码,因此它被证明是无效的。对我帮助最大的是 运行 aliyun
cli 实用程序和捕获流量,然后是 运行 具有完全相同参数的查询以比较确切的查询字符串。
但让我为我列出一些要点:
- 生成 hmac-sha1 sig 后,不要对其进行百分号编码,只需将其以正常形式的 www 编码添加到查询中即可
- HTTP 查询中参数的顺序并不重要;签名字符串中的参数顺序 是 虽然重要
- 我发现以下所有类型的请求都有效:GET,POST 参数在 URL 查询中,POST 参数在请求正文形式的 www 编码中;我在每个文档中使用 GET,但我看到 aliyun 使用 POST 与查询参数和查询中的有序参数
- 生成 HMAC-SHA1 时必须在密钥末尾添加
&
个字符
- 以二进制形式生成 HMAC-SHA1,然后编码为 Base64(无十六进制值)
- 一些参数可能不区分大小写,例如
Format
同时作为 json
和 JSON
- 我看到 aliyun、@wanghq 和 John 将 UUID 4 用于 SignatureNonce,但我推迟到纯随机(根据文档),因为它似乎只是一个重播攻击保护。所以加密安全的随机数一定是不必要的。
+
、*
和~
的特殊编码规则似乎只适用于签名字符串,实际上并没有在HTTP查询中以这种方式对数据进行编码。
我决定不使用@wanghq 的包装器,因为它对我也不起作用disables certificate validation,但也许它会被修复。只是我认为一旦签名被弄清楚,查询就足够简单了,额外的间接层是不值得的。对他的回答 +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")
代码可以稍微简化,但决定使其尽可能接近文档说明。
P.S。不知道为什么 John 删除了他的答案,但在他的网页上方留下 link 供任何 python 寻找示例代码的人使用
正在阅读HTTP API docs。我的请求因签名错误而失败。从错误消息中我可以看到我要签名的字符串是正确的,但看起来我无法生成正确的 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"
很明显,其中 none 与 CT9X0VtwR86fNWSnsc6v8YGOjuE=
的示例签名匹配。知道这里缺少什么吗?
更新: 从 Golang 客户端工具获取 tcpdump
我看到它做了一个 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
当我从上述请求中获取参数并生成签名时,它 匹配。所以我尝试了所有树:GET
、POST
和 URL 参数以及 POST
和正文中的参数。每次我收到签名错误。如果我使用与 golang 工具完全相同的参数重做请求,我会收到 nonce already used 错误(如预期的那样)。
- 好像是这个aliyun ruby sdk (non official, just for reference) works. You may want to check how it's implemented.
- 检查其 string_to_sign 的外观。我做了一个 运行,它似乎与您提供的略有不同。参数与
&
而不是%26
连接。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
require 'rubygems' require 'aliyun' $DEBUG = true options = { :access_key_id => "k", :access_key_secret => "s", :service => :ecs } service = Aliyun::Service.new options puts service.DescribeRegions({})
终于成功了。在我的案例中,主要问题是我一直在对签名参数进行双百分比编码,因此它被证明是无效的。对我帮助最大的是 运行 aliyun
cli 实用程序和捕获流量,然后是 运行 具有完全相同参数的查询以比较确切的查询字符串。
但让我为我列出一些要点:
- 生成 hmac-sha1 sig 后,不要对其进行百分号编码,只需将其以正常形式的 www 编码添加到查询中即可
- HTTP 查询中参数的顺序并不重要;签名字符串中的参数顺序 是 虽然重要
- 我发现以下所有类型的请求都有效:GET,POST 参数在 URL 查询中,POST 参数在请求正文形式的 www 编码中;我在每个文档中使用 GET,但我看到 aliyun 使用 POST 与查询参数和查询中的有序参数
- 生成 HMAC-SHA1 时必须在密钥末尾添加
&
个字符 - 以二进制形式生成 HMAC-SHA1,然后编码为 Base64(无十六进制值)
- 一些参数可能不区分大小写,例如
Format
同时作为json
和JSON
- 我看到 aliyun、@wanghq 和 John 将 UUID 4 用于 SignatureNonce,但我推迟到纯随机(根据文档),因为它似乎只是一个重播攻击保护。所以加密安全的随机数一定是不必要的。
+
、*
和~
的特殊编码规则似乎只适用于签名字符串,实际上并没有在HTTP查询中以这种方式对数据进行编码。
我决定不使用@wanghq 的包装器,因为它对我也不起作用disables certificate validation,但也许它会被修复。只是我认为一旦签名被弄清楚,查询就足够简单了,额外的间接层是不值得的。对他的回答 +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")
代码可以稍微简化,但决定使其尽可能接近文档说明。
P.S。不知道为什么 John 删除了他的答案,但在他的网页上方留下 link 供任何 python 寻找示例代码的人使用