Проблемы с генерацией подписи для облака 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 захватывает трафик, затем выполняет запрос с точно такими же параметрами, чтобы сравнить точную строку запроса.

Но позвольте мне перечислить некоторые ключевые моменты для меня:

  1. После генерации сигнатуры hmac-sha1 не кодируйте ее в процентах, просто добавьте ее в запрос с обычной кодировкой www
  2. порядок параметров в HTTP-запросе не имеет значения; порядок параметров в строке подписи является значительным, хотя
  3. Я нахожу все следующие типы запросов для работы: GET, POST с параметрами в URL-запросе, POST с параметрами в форме тела запроса в кодировке www; Я использую GET для документации, но я вижу, что aliyun использует параметры запроса POST против запроса и упорядоченные параметры в запросе
  4. ты должен добавить & символ до конца секретного ключа при генерации HMAC-SHA1
  5. генерировать HMAC-SHA1 в двоичном виде, затем кодировать как Base64 (без шестнадцатеричных значений)
  6. некоторые параметры могут быть нечувствительными к регистру, например Format работает как json а также JSON
  7. Я вижу, что Алиюн, @wanghq и Джон используют 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, ищущих пример кода

  • Кажется, это работает 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

Другие вопросы по тегам