

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 建立已簽署的 AWS API 請求
<a name="reference_sigv-create-signed-request"></a>

**重要**  
如果您使用 AWS SDK （請參閱[範例程式碼和程式庫](https://aws.amazon.com/developer/)) 或 AWS Command Line Interface (AWS CLI) 工具來傳送 API 請求 AWS，您可以略過本節，因為 SDK 和 CLI 用戶端會使用您提供的存取金鑰來驗證您的請求。除非有充分的理由不這樣做，否則建議您始終使用 SDK 或 CLI。  
在支援多個簽章版本的區域中，手動簽署要求意味著您必須指定要使用的簽章版本。當您向多區域存取點提供請求時，SDK 和 CLI 會自動切換為使用第 4A 版簽署程序，而不需其他組態。

您可以使用 AWS SigV4 簽署通訊協定來建立 AWS API 請求的已簽署請求。

1. 根據請求詳細資訊來建立正式請求。

1. 使用您的 AWS 登入資料計算簽章。

1. 將此簽章作為授權標頭新增至請求。

AWS 然後， 會複寫此程序，並驗證簽章、授予或拒絕相應存取。

若要了解如何使用 AWS SigV4 簽署 API 請求，請參閱 [請求簽章範例](reference_sigv-examples.md)。

下表描述了在建立已簽署請求的過程中所使用的函數。您需要為這些函數實作程式碼。如需詳細資訊，請參閱 [AWS SDK 中的程式碼範例](reference_sigv.md#reference_aws-signing-resources)。


| 函式 | Description | 
| --- | --- | 
|  `Lowercase()`  |  將字串轉換成小寫。  | 
|  `Hex()`  |  小寫基數 16 編碼。  | 
|  `SHA256Hash()`  |  安全雜湊演算法 (SHA) 加密雜湊函數。  | 
|  `HMAC-SHA256()`  |  使用 SHA256 演算法搭配提供的簽署金鑰來計算 HMAC。這是使用 SigV4 簽署時的最終簽章。  | 
|  `ECDSA-Sign`  |  橢圓曲線數位簽章演算法 (ECDSA) 簽章，使用以公有-私有金鑰密碼編譯為基礎的非對稱簽章進行計算。  | 
|  `KDF(K, Label, Context, L)`  |  使用 PRF 函數 HMAC-SHA256 的計數器模式下的 NIST SP800-108 KDF，如 [NIST SP 800-108r1](https://doi.org/10.6028/NIST.SP.800-108r1-upd1) 中所定義。  | 
|  `Oct2Int(byte[ ])`  |  八位元轉整數函數，如 ANSI X9.62 中所述。  | 
|  `Trim()`  |  移除任何前置和結尾空格。  | 
|  `UriEncode()`  |  URI 編碼每個位元組。UriEncode() 必須強制執行下列規則： [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_tw/IAM/latest/UserGuide/reference_sigv-create-signed-request.html)  由於基礎 RFC 中的實作差異和相關模糊性，您的開發平台提供的標準 UriEncode 函數可能無法工作。我們建議您編寫自己的自訂 UriEncode 函數，以確保您的編碼能夠正常工作。  若要查看 Java 中的 UriEncode 函數範例，請參閱 GitHub 網站上的 [Java 公用程式](https://github.com/aws/aws-sdk-java/blob/master/aws-java-sdk-core/src/main/java/com/amazonaws/util/SdkHttpUtils.java#L66)。  | 

**注意**  
簽署請求時，您可以使用 AWS SigV4 或 AWS SigV4a。兩者之間的主要差異取決於簽章的計算方式。對於 SigV4a，區域集包含在要簽署的字串中，但不是憑證衍生步驟的一部分。

## 具有臨時安全憑證的簽署請求
<a name="temporary-security-credentials"></a>

您可以使用由 AWS Security Token Service () 提供的臨時安全登入資料，而不是使用長期登入資料來簽署請求AWS STS。

當您使用臨時安全憑證時，必須將 `X-Amz-Security-Token` 新增至授權標頭或將它包含在查詢字串中，以保留工作階段權杖。某些服務會要求您將 `X-Amz-Security-Token` 新增至正式請求。其他服務只需要您在計算簽章之後於結尾新增 `X-Amz-Security-Token`。檢查每個 的文件 AWS 服務 ，了解特定需求。

## 簽署步驟摘要
<a name="create-signed-request-steps"></a>

**建立正式請求**  
將您的請求內容 (主機、動作、標頭等) 編排為標準正式格式。正式請求就是其中一個用於建立登入字串的輸入。如需有關建立正式請求的詳細資訊，請參閱 [AWS API 請求簽章的元素](reference_sigv-signing-elements.md)。

**建立正式請求的雜湊**  
使用與您用來建立承載雜湊相同的演算法，對正式請求進行雜湊。正式請求的雜湊是小寫十六進位字元字串。

**建立要簽署的字串**  
使用正式請求和額外資訊，例如演算法、請求日期、憑證範圍及正式請求的雜湊，建立登入字串。

**衍生簽署金鑰**  
使用私密存取金鑰衍生用於簽署請求的金鑰。

**計算簽章**  
使用衍生的簽署金鑰作為雜湊金鑰，對要簽署的字串執行金鑰式雜湊操作。

**將簽章新增至請求**  
將計算簽章新增至 HTTP 標頭，或新增至請求的查詢字串。

## 建立正式請求
<a name="create-canonical-request"></a>

若要建立正式請求，請串連以下字串 (以換行符號字元分隔)。這有助於確保您計算的簽章符合 AWS 計算的簽章。

```
<HTTPMethod>\n
<CanonicalURI>\n
<CanonicalQueryString>\n
<CanonicalHeaders>\n
<SignedHeaders>\n
<HashedPayload>
```
+ *HTTPMethod* – HTTP 方法，例如 `GET`、`PUT`、`HEAD` 和 `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` 包含在您的請求中。您必須將此標頭新增在 *CanonicalHeader* 的清單中。
  + 對於 SigV4a，您必須包含區域集標頭，指定請求將在其中生效的區域集。標頭 `X-Amz-Region-Set` 將指定為逗號分隔值的清單。下列範例展示了區域標頭，其允許在 us-east-1 和 us-west-1 區域中發出請求。

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

    您可以在區域中使用萬用字元 (\$1) 來指定多個區域。在下列範例中，標頭允許在 us-west-1 和 us-west-2 中發出請求。

    `X-Amz-Region-Set=us-west-*`
**注意**  
Amazon S3 AWS 請求需要 `x-amz-content-sha256` 標頭。其提供請求承載的雜湊。如果沒有承載，您必須提供空字串的雜湊。

  每個標頭名稱必須：
  + 使用小寫字元。
  + 依字母順序顯示。
  + 後接冒號 (`:`)。

  對於值，您必須：
  + 修剪任何前置或結尾空格。
  + 將連續空格轉換為單一空格。
  + 使用逗號分隔多值標頭的值。
  + 簽章中必須包含主機標頭 (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-*` 標頭；但是，為了防止資料竄改，應考慮在簽章計算中包含其他標頭。  
請勿包含跨複雜系統傳輸期間經常變更的逐個躍點標頭。這包括所有易變的傳輸標頭，這些標頭可能會由代理、負載平衡器和分散式系統中的節點變更，包括 `connection`、`x-amzn-trace-id`、`user-agent`、`keep-alive`、`transfer-encoding`、`TE`、`trailer`、`upgrade`、`proxy-authorization` 和 `proxy-authenticate`。
+ *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"))`

## 建立正式請求的雜湊
<a name="create-canonical-request-hash"></a>

使用與您用來建立承載雜湊相同的演算法，建立正式請求的雜湊 (摘要)。正式請求的雜湊是小寫十六進位字元字串。

## 建立要簽署的字串
<a name="create-string-to-sign"></a>

若要建立要簽署的字串，請串連以下字串 (以換行符號字元分隔)。請勿以換行符號字元結束此字串。

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

## 衍生簽署金鑰
<a name="derive-signing-key"></a>

若要衍生簽署金鑰，請選擇下列其中一個程序來計算 SigV4 或 SigV4a 的簽署金鑰。

### 衍生 SigV4 的簽署金鑰
<a name="derive-signing-key-sigv4"></a>

若要衍生 SigV4 的簽署金鑰，請在請求日期、區域和服務上執行連續的金鑰雜湊操作 (HMAC)，並將 AWS 私密存取金鑰作為初始雜湊操作的金鑰。

對於每個步驟，使用所需金鑰和資料呼叫雜湊函數。對雜湊函數的每次呼叫結果都會變成雜湊函數的下一次呼叫輸入。

下列範例顯示如何衍生此程序下一節中使用的 `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`)。

  如需區域字串清單，請參閱《AWS 一般參考》中的[區域端點](https://docs.aws.amazon.com//general/latest/gr/rande.html#regional-endpoints)。**
+ `Service` – 包含服務代碼的字串 (例如 `ec2`)。
+ 在之前步驟中建立的登入字串。

**衍生 SigV4 的簽署金鑰**

1. 串連 `"AWS4"` 和私密存取金鑰。呼叫雜湊函數，將串連的字串作為索引鍵，日期字串做為資料。

   ```
   DateKey = hash("AWS4" + Key, Date)
   ```

1. 呼叫雜湊函數，將上一次呼叫的結果作為索引鍵，區域字串作為資料。

   ```
   DateRegionKey = hash(kDate, Region)
   ```

1. 呼叫雜湊函數，將上一次呼叫的結果作為索引鍵，服務字串作為資料。

   服務代碼由服務定義。可以使用 *AWS Pricing CLI* 中的 [get-products](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/pricing/get-products.html) 來傳回服務的服務代碼。

   ```
   DateRegionServiceKey = hash(kRegion, Service)
   ```

1. 呼叫雜湊函數，將上一次呼叫的結果作為索引鍵，"aws4\$1request" 作為資料。

   ```
   SigningKey = hash(kService, "aws4_request")
   ```

### 衍生 SigV4a 的簽署金鑰
<a name="derive-signing-key-sigv4a"></a>

若要建立 SigV4a 的簽署金鑰，請使用下列程序從私密存取金鑰衍生金鑰對。如需此衍生的實作範例，請參閱[AWS 用戶端身分驗證的 C99 程式庫實作](https://github.com/awslabs/aws-c-auth/blob/e8360a65e0f3337d4ac827945e00c3b55a641a5f/source/key_derivation.c#L291.) 

```
n = [NIST P-256 elliptic curve group order]
G = [NIST P-256 elliptic curve base point]
label = "AWS4-ECDSA-P256-SHA256"

akid = [AWS access key ID as a UTF8 string]
sk = [AWS 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
}
```

## 計算簽章
<a name="calculate-signature"></a>

衍生簽署金鑰之後，即可計算要新增至請求的簽章。此程序根據所使用的簽章版本而有所不同。

**計算 SigV4 的簽章**

1. 呼叫雜湊函數，將上一次呼叫的結果作為索引鍵，**待簽署字串**作為資料。使用衍生的金鑰做為此操作的雜湊金鑰。結果是作為二進位值的簽章。

   ```
   signature = hash(SigningKey, string-to-sign)
   ```

1. 將簽章的表示形式從二進位轉換為十六進位，使用小寫字元。

**計算 SigV4a 的簽章**

1. 使用數位簽署演算法 (ECDSA P-256)，簽署上一步驟中建立的**待簽署字串**。用於此簽章的金鑰是衍生自上述私密存取金鑰的私有非對稱金鑰。

   ```
   signature = base16(ECDSA-Sign(k, string-to-sign))
   ```

1. 將簽章的表示形式從二進位轉換為十六進位，使用小寫字元。

## 將簽章新增至請求
<a name="add-signature-to-request"></a>

將計算簽章新增至您的請求。

**Example 範例：授權標頭**  
**SigV4**  
下列範例顯示使用 AWS 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**  
下列範例顯示使用 AWS 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
```

**Example 範例：查詢字串中包含身分驗證參數的請求**  
**SigV4**  
下列範例顯示使用 AWS 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**  
下列範例展示了針對 `CreateBucket` 動作使用 AWS SigV4a 的查詢，其中包含身分驗證資訊。為了便於閱讀，此範例使用換行符號進行格式化，而不是 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
```