创建已签名的 Amazon API 请求 - Amazon Identity and Access Management
Amazon Web Services 文档中描述的 Amazon Web Services 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅 中国的 Amazon Web Services 服务入门 (PDF)

创建已签名的 Amazon API 请求

重要

如果您使用 Amazon SDK(请参阅示例代码和库)或 Amazon Command Line Interface(Amazon CLI)工具向 Amazon 发送 API 请求,则可以跳过本部分,因为 SDK 和 CLI 客户端会使用您提供的访问密钥来验证您的请求。除非您有充分的理由不这样做,否则我们建议您始终使用 SDK 或 CLI。

在支持多个签名版本的区域中,手动签名请求意味着您必须指定使用的签名版本。当您向多区域访问点提供请求时,SDK 和 CLI 会自动切换为使用签名版本 4A,而无需进行其他配置。

您可以使用 Amazon SigV4 签名协议为 Amazon API 请求创建签名的请求。

  1. 根据请求详细信息创建规范请求。

  2. 使用 Amazon 凭证计算签名。

  3. 将此签名作为授权标头添加到请求。

然后 Amazon 复制此过程并验证签名,相应地授予或拒绝访问权限。

要了解如何使用 Amazon SigV4 对 API 请求进行签名,请参阅 请求签名示例

下表介绍了创建签名请求过程中使用的函数。您需要为这些函数实现代码。如需了解更多信息,请参阅 Amazon 软件开发工具包中的代码示例

函数 描述

Lowercase()

将字符串转换为小写。

Hex()

base-16 编码的小写形式。

SHA256Hash()

安全哈希算法(SHA)加密哈希函数。

HMAC-SHA256()

使用 SHA256 算法和提供的签名密钥计算 HMAC。这是您使用 SigV4 签名的最终签名。

ECDSA-Sign

使用基于公私密钥加密的非对称签名计算椭圆曲线数字签名算法(ECDSA)签名。

KDF(K, Label, Context, L)

处于计数器模式的 NIST SP800-108 KDF 使用 NIST SP 800-108r1 中定义的 PRF 函数 HMAC-SHA256。

Oct2Int(byte[ ])

ANSI X9.62 中描述的整数函数的八位字节。

Trim()

删除所有前导空格或尾随空格。

UriEncode()

URI 对每个字节进行编码。UriEncode() 必须强制执行以下规则:

  • URI 对除非预留字符之外的所有字节进行编码:“A”-“Z”、“a”-“z”、“0”-“9”、“-”、“.”、“_”和“~”。

  • 空格字符是预留字符,必须编码为“%20”(而不是“+”)。

  • 每个 URI 编码字节由“%”和该字节的两位十六进制值组成。

  • 十六进制值中的字母必须为大写,例如“%1A”。

  • 对除对象键名称之外的所有位置的正斜杠字符“/”进行编码。例如,如果对象键名称是 photos/Jan/sample.jpg,则不会对键名称中的正斜杠进行编码。

重要

由于底层 RFC 中的实现差异和相关歧义,您开发平台提供的标准 UrienCode 函数可能无法正常工作。建议您编写自己的自定义 UrienCode 函数,以确保编码能够正常工作。

要查看 Java 中的 UriEncode 函数示例,请参阅 GitHub 网站上的 Java Utilities

注意

签署请求时,您可以使用 Amazon SigV4 或 Amazon SigV4a。两者之间的关键区别取决于签名的计算方式。使用 SigV4a 时,区域集包含在待签字符串中,但不是凭证派生步骤的一部分。

使用临时安全凭证签名请求

您可使用 Amazon Security Token Service(Amazon STS)提供的临时安全凭证来签署请求,而不是使用长期凭证。

使用临时安全凭证时,必须将 X-Amz-Security-Token 添加至授权标头或将其包含在查询字符串中以保存会话令牌。某些服务会要求您将 X-Amz-Security-Token 添加至规范请求。其他服务仅会要求您在计算出签名后在末尾添加 X-Amz-Security-Token。有关具体要求,请查看每个 Amazon Web Services 服务的文档。

签名步骤摘要

创建规范请求

将请求的内容(主机、操作、标头等)组织为标准规范格式。规范请求是用于创建待签字符串的输入之一。有关创建规范请求的详细信息,请参阅Amazon API 请求签名的元素

创建规范请求的哈希值

使用创建负载的哈希时所使用的相同算法来哈希规范请求。经过哈希处理的规范请求必须以小写十六进制字符串形式表示。

创建待签字符串

使用规范请求和额外信息(例如算法、请求日期、凭证范围和规范请求的哈希)创建待签字符串。

派生签名密钥

使用秘密访问密钥派生用于对请求进行签名的密钥。

计算签名

使用派生的签名密钥作为哈希密钥,对待签字符串执行加密哈希操作。

将签名添加至请求

将计算的签名添加到请求的 HTTP 标头或查询字符串中。

创建规范请求

要创建规范请求,请串联由换行符分隔的以下字符串。这有助于确保您计算出的签名能够与 Amazon 计算出的签名相匹配。

<HTTPMethod>\n <CanonicalURI>\n <CanonicalQueryString>\n <CanonicalHeaders>\n <SignedHeaders>\n <HashedPayload>
  • HTTPMethod – HTTP 方法,例如 GETPUTHEAD 和 DELETE

  • CanonicalUri:绝对路径组件 URI 的 URI 编码版本,以域名后面的 / 开头,直至字符串结尾处,或者如果包含查询字符串参数,则直至问号字符(?)。如果绝对路径为空,则使用正斜杠字符(/)。以下示例中的 URI /amzn-s3-demo-bucket/myphoto.jpg 是绝对路径,并且您无需在绝对路径中对 / 进行编码:

    http://s3.amazonaws.com/amzn-s3-demo-bucket/myphoto.jpg
  • CanonicalQueryString – URI 编码的查询字符串参数。您可以单独对每个名称和值进行 URI 编码。您还必须按键名称的字母顺序对规范查询字符串中的参数进行排序。编码后进行排序。以下 URI 示例中的查询字符串是:

    http://s3.amazonaws.com/amzn-s3-demo-bucket?prefix=somePrefix&marker=someMarker&max-keys=2

    规范查询字符串如下所示(为便于阅读,此示例中添加了换行符:):

    UriEncode("marker")+"="+UriEncode("someMarker")+"&"+ UriEncode("max-keys")+"="+UriEncode("20") + "&" + UriEncode("prefix")+"="+UriEncode("somePrefix")

    当请求针对子资源时,相应的查询参数值将为空字符串("")。例如,以下 URI 标识了 amzn-s3-demo-bucket 存储桶上的 ACL 子资源:

    http://s3.amazonaws.com/amzn-s3-demo-bucket?acl

    在这种情况下,CanonicalQueryString 将为:

    UriEncode("acl") + "=" + ""

    如果 URI 不包含 ?,则请求中没有查询字符串,并且您需要将规范查询字符串设置为空字符串("")。您仍然需要包含换行符 ("\n")。

  • CanonicalHeaders:请求标头及其值的列表。各个标头名称和值对用换行符("\n")分隔。以下是 CanonicalHeader 的示例:

    Lowercase(<HeaderName1>)+":"+Trim(<value>)+"\n" Lowercase(<HeaderName2>)+":"+Trim(<value>)+"\n" ... Lowercase(<HeaderNameN>)+":"+Trim(<value>)+"\n"

    CanonicalHeaders 列表必须包含以下内容:

    • HTTP host 标头。

    • 如果请求中存在 Content-Type 标头,则必须将其添加到 CanonicalHeaders 列表中。

    • 此外,还必须添加计划在请求中包含的所有 x-amz-* 标头。例如,如果您使用临时安全凭证,则请求中必须包含 x-amz-security-token。您必须将此标头添加到 CanonicalHeaders 列表中。

    • 对于 SigV4a,您必须包含一个区域集标头,该标头指定请求将在哪一组区域中生效。标头 X-Amz-Region-Set 被指定为逗号分隔值的列表。下面的示例显示一个区域标头,该标头允许在 us-east-1 和 us-west-1 区域中进行请求。

      X-Amz-Region-Set=us-east-1,us-west-1

      您可以在区域中使用通配符(*)来指定多个区域。在下面的示例中,标头允许在 us-west-1 和 us-west-2 中进行请求。

      X-Amz-Region-Set=us-west-*

    注意

    x-amz-content-sha256 标题是 Amazon S3 Amazon 请求所必需的。它将提供请求负载的哈希。如果不包含有效负载,则必须提供空字符串的哈希值。

    每个标头名称必须:

    • 使用小写字符。

    • 按字母顺序显示。

    • 后跟冒号(:)。

    对于值,您必须:

    • 去除任何前导空格或尾随空格。

    • 将连续空格转换为单个空格。

    • 使用逗号分隔多值标头的值。

    • 签名中必须包含 host 标头(HTTP/1.1)或 :authority 标头(HTTP/2)以及任何 x-amz-* 标头。签名中也可以包含其他标准标头,例如 content-type。

    上一部分介绍了本示例中使用的 Lowercase() 和 Trim() 函数。

    以下是示例 CanonicalHeaders 字符串。标头名称为小写且已排序。

    host:s3.amazonaws.com x-amz-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 x-amz-date:20130708T220855Z

    注意

    为了计算授权签名,只有主机以及任何 x-amz-* 标头为必填项;但是,为了防止数据篡改,您应该考虑在签名计算中包含所有标头。

  • SignedHeaders:按字母顺序排序、以分号分隔的小写请求标头名称列表。列表中的请求标头与您在 CanonicalHeaders 字符串中包含的标头相同。对于前面的示例,SignedHeaders 的值如下:

    host;x-amz-content-sha256;x-amz-date
  • HashedPayload – 使用 HTTP 请求正文中的负载作为哈希函数的输入创建的字符串。此字符串使用小写十六进制字符。

    Hex(SHA256Hash(<payload>>))

    如果请求中不包含有效负载,则计算空字符串的哈希值,例如,当使用 GET 请求检索对象时,有效负载中没有任何内容。

    Hex(SHA256Hash(""))
    注意

    对于 Amazon S3,请在构造规范请求时包含文字字符串 UNSIGNED-PAYLOAD,并在发送请求时设置与 x-amz-content-sha256 标头值相同的值。

    Hex(SHA256Hash("UNSIGNED-PAYLOAD"))

创建规范请求的哈希值

使用创建负载的哈希时所使用的相同算法来创建规范请求的哈希(摘要)。经过哈希处理的规范请求必须以小写十六进制字符串形式表示。

创建待签字符串

要创建待签字符串,请串联以下由换行符分隔的以下字符串。请勿使用换行符作为此字符串的结尾。

Algorithm \n RequestDateTime \n CredentialScope \n HashedCanonicalRequest
  • Algorithm – 用于创建规范请求的哈希的算法。

    • SigV4 – 使用 AWS4-HMAC-SHA256 指定 HMAC-SHA256 哈希算法。

    • SigV4a – 使用 AWS4-ECDSA-P256-SHA256 指定 ECDSA-P256-SHA-256 哈希算法。

  • RequestDateTime – 在凭证范围内使用的日期和时间。该值是采用 ISO 8601 格式的当前 UTC 时间(例如 20130524T000000Z)。

  • CredentialScope:凭证范围,将生成的签名限制在指定的区域和服务范围内。

    • SigV4 – 凭证包括访问密钥 ID、YYYYMMDD 格式的日期、区域代码、服务代码和 aws4_request 终止字符串,用斜杠(/)分隔。区域代码、服务代码和终止字符串必须使用小写字符。字符串具有以下格式:YYYYMMDD/region/service/aws4_request

    • SigV4a – 凭证包括 YYYYMMDD 格式的日期、服务名称和 aws4_request 终止字符串,用斜杠(/)分隔。请注意,凭证范围不包括区域,因为该区域包含在单独的标头 X-Amz-Region-Set 中。字符串具有以下格式:YYYYMMDD/service/aws4_request

  • HashedCanonicalRequest:上一步中计算出的规范请求的哈希。

以下是要签名的字符串的示例。

"<Algorithm>" + "\n" + timeStampISO8601Format + "\n" + <Scope> + "\n" + Hex(<Algorithm>(<CanonicalRequest>))

派生签名密钥

要派生签名密钥,请选择以下过程之一来计算 SigV4 或 SigV4a 的签名密钥。

派生 SigV4 的签名密钥

要派生 SigV4 的签名密钥,请使用 Amazon 秘密访问密钥作为初始哈希操作的密钥,对请求日期、区域和服务执行一系列加密哈希操作(HMAC)。

对于每个步骤,使用所需的密匙和数据调用哈希函数。每次调用哈希函数的结果都会变成下一次调用哈希函数的输入。

以下示例说明了如何派生本过程下一部分中使用的 SigningKey,并说明了输入的串联和哈希顺序。HMAC-SHA256 是用于对数据进行哈希处理的哈希功能,如下所示。

DateKey = HMAC-SHA256("AWS4"+"<SecretAccessKey>", "<YYYYMMDD>") DateRegionKey = HMAC-SHA256(<DateKey>, "<aws-region>") DateRegionServiceKey = HMAC-SHA256(<DateRegionKey>, "<aws-service>") SigningKey = HMAC-SHA256(<DateRegionServiceKey>, "aws4_request")
必填项
  • Key – 包含您的秘密访问密钥的字符串。

  • Date – 包含在凭证范围中使用的日期的字符串,格式为 YYYYMMDD

  • Region – 包含区域代码的字符串(例如,us-east-1)。

    有关区域字符串的列表,请参阅 Amazon Web Services 一般参考 中的 Regional Endpoints

  • Service – 包含服务代码的字符串(例如,ec2)。

  • 在上一步中创建的要签名的字符串。

要派生 SigV4 的签名密钥
  1. 串联 "AWS4" 和秘密访问密钥。使用密钥和数据调用哈希函数,并将连接的字符串作为密钥,而日期字符串作为数据。

    DateKey = hash("AWS4" + Key, Date)
  2. 使用密钥和数据调用哈希函数,并将上一次调用的结果作为密钥,而区域字符串作为数据。

    DateRegionKey = hash(kDate, Region)
  3. 使用密钥和数据调用哈希函数,并将上一次调用的结果作为密钥,而服务字符串作为数据。

    服务代码由服务定义。您可以在 Amazon Pricing CLI 中使用 get-products 返回服务的服务代码。

    DateRegionServiceKey = hash(kRegion, Service)
  4. 使用密钥和数据调用哈希函数,并将上一次调用的结果作为密钥,而“aws4_request”作为数据。

    SigningKey = hash(kService, "aws4_request")

派生 SigV4a 的签名密钥

要为 SigV4a 创建签名密钥,请使用以下过程从秘密访问密钥派生密钥对。有关此派生实现的示例,请参阅 Amazon 客户端身份验证的 C99 库实现

n = [NIST P-256 elliptic curve group order] G = [NIST P-256 elliptic curve base point] label = "AWS4-ECDSA-P256-SHA256" akid = [Amazon access key ID as a UTF8 string] sk = [Amazon secret access Key as a UTF8 Base64 string] input_key = "AWS4A" || sk count = 1 while (counter != 255) { context = akid || counter // note: counter is one byte key = KDF(input_key, label, context, 256) c = Oct2Int(key) if (c > n - 2) { counter++ } else { k = c + 1 // private key Q = k * G // public key } } if (c < 255) { return [k, Q] } else { return FAILURE }

计算签名

派生签名密钥后,计算要添加到请求中的签名。此过程因您使用的签名版本而不同。

要计算 SigV4 的签名
  1. 将上一次调用的结果作为密钥,要签名的字符串作为数据来调用哈希函数。使用派生的签名密钥作为此操作的哈希密钥。结果是作为二进制值的签名。

    signature = hash(SigningKey, string-to-sign)
  2. 将签名从二进制转换为十六进制表示形式,使用小写字符。

要计算 SigV4a 的签名
  1. 使用数字签名算法(ECDSA P-256),对您在上一步中创建的要签名的字符串进行签名。用于此签名的密钥是从上述秘密访问密钥派生的非对称私有密钥。

    signature = base16(ECDSA-Sign(k, string-to-sign))
  2. 将签名从二进制转换为十六进制表示形式,使用小写字符。

将签名添加至请求

将计算出的签名添加到您的请求中。

例 示例:授权标头
SigV4

以下示例显示了使用 Amazon SigV4 的 DescribeInstances 操作的 Authorization 标头。为便于阅读,此示例已使用换行符编排过格式。在您的代码中,这必须是连续的字符串。算法和 Credential 之间没有逗号。但是,必须使用逗号分隔其他元素。

Authorization: AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20220830/us-east-1/ec2/aws4_request, SignedHeaders=host;x-amz-date, Signature=calculated-signature
SigV4a

以下示例显示了使用 Amazon SigV4a 的 CreateBucket 操作的授权标头。为便于阅读,此示例已使用换行符编排过格式。在您的代码中,这必须是连续的字符串。算法和凭证之间没有逗号。但是,必须使用逗号分隔其他元素。

Authorization: AWS4-ECDSA-P256-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20220830/s3/aws4_request, SignedHeaders=host;x-amz-date;x-amz-region-set, Signature=calculated-signature
例 示例:请求在查询字符串中使用身份验证参数
SigV4

以下示例显示了对包含身份验证信息的使用 Amazon SigV4 的 DescribeInstances 操作的查询。为便于阅读,此示例已使用换行符编排过格式,而非 URL 编码。在您的代码中,查询字符串必须是采用 URL 编码的连续字符串。

https://ec2.amazonaws.com/? Action=DescribeInstances& Version=2016-11-15& X-Amz-Algorithm=AWS4-HMAC-SHA256& X-Amz-Credential=AKIAIOSFODNN7EXAMPLE/20220830/us-east-1/ec2/aws4_request& X-Amz-Date=20220830T123600Z& X-Amz-SignedHeaders=host;x-amz-date& X-Amz-Signature=calculated-signature
SigV4a

以下示例显示了对包含身份验证信息的使用 Amazon SigV4a 的 CreateBucket 操作的查询。为便于阅读,此示例已使用换行符编排过格式,而非 URL 编码。在您的代码中,查询字符串必须是采用 URL 编码的连续字符串。

https://ec2.amazonaws.com/? Action=CreateBucket& Version=2016-11-15& X-Amz-Algorithm=AWS4-ECDSA-P256-SHA256& X-Amz-Credential=AKIAIOSFODNN7EXAMPLE/20220830/s3/aws4_request& X-Amz-Region-Set=us-west-1& X-Amz-Date=20220830T123600Z& X-Amz-SignedHeaders=host;x-amz-date;x-amz-region-set& X-Amz-Signature=calculated-signature