Проблемы с генерацией подписи для облака alibaba
Чтение документов 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=
, Есть идеи, чего здесь не хватает?
Обновление: принимая tcpdump
из инструмента клиента Golang я вижу, что он делает 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, я получаю одноразовую ошибку (как и ожидалось).
3 ответа
Наконец-то все заработало. Основной проблемой в моем случае было то, что я дважды кодировал параметр подписи, поэтому он оказался недействительным. Больше всего мне помогало aliyun
Утилита cli захватывает трафик, затем выполняет запрос с точно такими же параметрами, чтобы сравнить точную строку запроса.
Но позвольте мне перечислить некоторые ключевые моменты для меня:
- После генерации сигнатуры hmac-sha1 не кодируйте ее в процентах, просто добавьте ее в запрос с обычной кодировкой www
- порядок параметров в HTTP-запросе не имеет значения; порядок параметров в строке подписи является значительным, хотя
- Я нахожу все следующие типы запросов для работы: GET, POST с параметрами в URL-запросе, POST с параметрами в форме тела запроса в кодировке www; Я использую GET для документации, но я вижу, что aliyun использует параметры запроса POST против запроса и упорядоченные параметры в запросе
- ты должен добавить
&
символ до конца секретного ключа при генерации HMAC-SHA1 - генерировать HMAC-SHA1 в двоичном виде, затем кодировать как Base64 (без шестнадцатеричных значений)
- некоторые параметры могут быть нечувствительными к регистру, например
Format
работает какjson
а такжеJSON
- Я вижу, что Алиюн, @wanghq и Джон используют UUID 4 для SignatureNonce, но я отложил случайное (в соответствии с документацией), потому что, похоже, это всего лишь защита от повторной атаки. Так что криптографически безопасное случайное число должно быть ненужным.
- Специальные правила кодирования для
+
,*
а также~
похоже, применяются только к строке для подписи, но не для кодирования данных таким способом в 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, ищущих пример кода
- Кажется, это работает aliyun ruby sdk (неофициальный, просто для справки). Вы можете проверить, как это реализовано.
- Проверьте, как выглядит его 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
требовать 'rubygems' требовать 'aliyun' $DEBUG = true варианты = {:access_key_id => "k",:access_key_secret => "s",:service =>:ecs } service = Aliyun::Service.new опции ставит сервис. Опишите регионы ({})
хотел поделиться найденной библиотекой (Python
), который делает все за меня без необходимости подписывать запрос самостоятельно. Это также может помочь тем, кто хочет просто скопировать свои функции и при этом создать подпись самостоятельно.
Я использую это:
from aliyunsdkcore.client import AcsClient
from aliyunsdkvpc.request.v20160428.DescribeEipAddressesRequest import DescribeEipAddressesRequest
client = AcsClient(access_key, secret_key, region)
request = DescribeEipAddressesRequest()
request.set_accept_format('json')
response = client.do_action_with_exception(request) # FYI returned as Bytes
print(response)
У каждого раздела в Alibaba Cloud есть своя библиотека (точно так же, как я использовал:aliyunsdkvpc
для адресов EIP) И все они перечислены здесь: https://develop.aliyun.com/tools/sdk?#/python