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

创建已签名的 Amazon API 请求

重要

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

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

下文概述了创建已签名的请求的流程。要计算签名,首先需要一个待签字符串。然后,使用签名密钥计算待签字符串的 HMAC-SHA256 哈希值。下图演示了该过程,包括您为签名而创建的字符串的各个组成部分。


                规范请求、待签字符串、签名密钥和签名各部分的图像。

下表介绍了图中显示的函数。您需要为这些函数实现代码。如需了解更多信息,请参阅 Amazon 软件开发工具包中的代码示例

函数 描述

Lowercase()

将字符串转换为小写。

Hex()

base-16 编码的小写形式。

SHA256Hash()

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

HMAC-SHA256()

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

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 签名版本 4 或 Amazon 签名版本 4A。两者之间的关键区别取决于签名的计算方式。对于 Amazon 签名版本 4A,签名不包含区域特定的信息,并且使用 Amazon4-ECDSA-P256-SHA256 算法计算得出。

临时安全凭证

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

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

签名步骤摘要

第 1 步:创建规范请求

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

第 2 步:创建规范请求的哈希

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

步骤 3:创建要签名的字符串

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

第 4 步:计算签名

在派生签名密钥后,通过对待签字符串执行加密哈希操作来计算签名。使用派生的签名密钥作为此操作的哈希密钥。

第 5 步:将签名添加至请求。

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

第 1 步:创建规范请求

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

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

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

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

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

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

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

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

    http://s3.amazonaws.com/examplebucket?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 列表中。

    注意

    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>)

    如果请求中不包含有效负载,则按如下方式计算空字符串的哈希值:

    Hex(SHA256Hash(""))

    哈希值将返回以下值:

    e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

    例如,当您使用 PUT 请求上传对象时,您需要在正文中提供对象数据。当您使用 GET 请求检索对象时,您将计算空字符串的哈希值。

第 2 步:创建规范请求的哈希

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

第 3 步:创建要签名的字符串

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

Algorithm \n RequestDateTime \n CredentialScope \n HashedCanonicalRequest
  • Algorithm – 用于创建规范请求的哈希的算法。对于 SHA-256,算法是 AWS4-HMAC-SHA256

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

  • CredentialScope – 凭证范围。这会将生成的签名限制在指定的区域和服务范围内。该字符串采用以下格式:YYYYMMDD/region/service/aws4_request。

  • HashedCanonicalRequest – 规范请求的哈希。该值在步骤 2 中计算。

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

"AWS4-HMAC-SHA256" + "\n" + timeStampISO8601Format + "\n" + <Scope> + "\n" + Hex(SHA256Hash(<CanonicalRequest>))

第 4 步:计算签名

在 Amazon 签名版本 4 中,您将创建一个仅限于特定区域和服务的签名密钥,作为身份验证信息添加到请求中,而不会使用 Amazon 访问密钥签署请求。

DateKey = HMAC-SHA256("AWS4"+"<SecretAccessKey>", "<YYYYMMDD>") DateRegionKey = HMAC-SHA256(<DateKey>, "<aws-region>") DateRegionServiceKey = HMAC-SHA256(<DateRegionKey>, "<aws-service>") SigningKey = HMAC-SHA256(<DateRegionServiceKey>, "aws4_request")

有关区域代码的列表,请参阅《Amazon General Reference》中的 Regional Endpoints

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

必填项
  • 字符串 Key,其中包含您的秘密访问密钥

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

  • 字符串 Region,其中包含区域代码(例如,us-east-1

  • 字符串 Service,其中包含服务代码(例如,ec2

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

计算签名
  1. 连接“AWS4”和秘密访问密钥。使用密钥和数据调用哈希函数,并将连接的字符串作为密钥,而日期字符串作为数据。

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

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

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

    kSigning = hash(kService, "aws4_request")
  5. 使用密钥和数据调用哈希函数,并将上一次调用的结果作为密钥,而要签名的字符串作为数据。结果是作为二进制值的签名。

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

第 5 步:将签名添加至请求。

例 示例:授权标头

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

Authorization: AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20220830/us-east-1/ec2/aws4_request, SignedHeaders=host;x-amz-date, Signature=calculated-signature
例 示例:请求在查询字符串中使用身份验证参数

以下示例显示了对包含身份验证信息的 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

Amazon SDK 中的源代码

Amazon SDK 包括 GitHub 上用于签署 Amazon API 请求的源代码。有关代码示例,请参阅 Amazon 示例存储库中的示例项目