

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

# 驗證 Amazon SNS 訊息的簽章
<a name="sns-verify-signature-of-message"></a>

Amazon SNS 使用訊息簽章來確認傳送至 HTTP 端點的訊息真實性。為了確保訊息完整性並防止詐騙，**您必須在**處理任何 Amazon SNS 訊息之前驗證簽章。

**何時應驗證 Amazon SNS 簽章？**

在下列案例中，您應該驗證 Amazon SNS 訊息簽章：
+ 當 Amazon SNS 傳送通知訊息到您的 HTTP(S) 端點時。
+ 當 Amazon SNS 在 [https://docs.aws.amazon.com/sns/latest/api/API_Subscribe.html](https://docs.aws.amazon.com/sns/latest/api/API_Subscribe.html)或 [https://docs.aws.amazon.com/sns/latest/api/API_Unsubscribe.html](https://docs.aws.amazon.com/sns/latest/api/API_Unsubscribe.html) API 呼叫後傳送確認訊息到您的端點時。

**Amazon SNS 支援兩個簽章版本：**
+ SignatureVersion1 – 使用訊息的SHA1雜湊。
+ SignatureVersion2 – 使用訊息的SHA256雜湊。這可提供更強大的安全性，並且是建議的選項。

**若要正確驗證 SNS 訊息簽章，請遵循下列最佳實務：**
+ 一律使用 HTTPS 擷取簽署憑證，以防止未經授權的攔截攻擊。
+ 檢查憑證是否由 Amazon SNS 發行。
+ 確認憑證的信任鏈有效。
+ 憑證應該來自 SNS 簽署的 URL。
+ 未經驗證，請勿信任訊息中提供的任何憑證。
+ 拒絕任何具有非預期的訊息`TopicArn`，以防止詐騙。
+ Amazon SNS AWS SDKs 提供內建驗證邏輯，可降低實作錯誤的風險。

# 在 Amazon SNS 主題上設定訊息簽章版本
<a name="sns-verify-signature-of-message-configure-message-signature"></a>

在 Amazon SNS 主題上設定訊息簽章版本，可讓您增強訊息驗證程序的安全性和相容性。

在 `SignatureVersion`**1** (SHA1) 和 `SignatureVersion`**2** (SHA256) 之間選取，以控制用於簽署訊息的雜湊演算法。Amazon SNS 主題預設為 `SignatureVersion`**1**。您可以使用 [https://docs.aws.amazon.com/sns/latest/api/API_SetTopicAttributes.html](https://docs.aws.amazon.com/sns/latest/api/API_SetTopicAttributes.html) API 動作來設定此設定。

使用以下範例，`SignatureVersion`使用 設定主題屬性 AWS CLI：

```
aws sns set-topic-attributes \
    --topic-arn arn:aws:sns:us-east-2:123456789012:MyTopic \
    --attribute-name SignatureVersion \
    --attribute-value 2
```

# 在使用 HTTP 查詢型請求時，驗證 Amazon SNS 訊息的簽章
<a name="sns-verify-signature-of-message-verify-message-signature"></a>

在使用 HTTP 查詢型請求時，驗證 Amazon SNS 訊息的簽章可確保訊息的真實性和完整性。此程序會確認訊息來自 Amazon SNS，且未在傳輸期間遭到竄改。透過剖析訊息、建構正確的簽署字串，以及針對信任的公有金鑰驗證簽章，您可以保護您的系統免於詐騙和未經授權的訊息變更。

1. 從 Amazon SNS 傳送的 HTTP POST 請求內文中的 JSON 文件擷取**金鑰/值對**。這些欄位是建構**要簽署的字串**的必要欄位。
   + `Message`
   + `Subject` （如果有）
   + `MessageId`
   + `Timestamp`
   + `TopicArn`
   + `Type`

   例如：

   ```
   MESSAGE_FILE="message.json"
   FIELDS=("Message" "MessageId" "Subject" "Timestamp" "TopicArn" "Type")
   ```
**注意**  
如果任何欄位包含逸出字元 （例如 `\n`)，請將它們轉換為**原始格式**，以確保完全相符。

1. 在 Amazon SNS 訊息中尋找 `SigningCertURL` 欄位。此憑證包含驗證訊息簽章所需的公有金鑰。例如：

   ```
   SIGNING_CERT_URL=$(jq -r '.SigningCertURL' "$MESSAGE_FILE")
   ```

1. 確定 `SigningCertURL` 來自信任的 AWS 網域 （例如 https://sns.us-east-1.amazonaws.com)。基於安全考量，拒絕** AWS 網域外**的任何 URLs。

1. 從提供的 URL 下載 **X.509 憑證**。例如：

   ```
   curl -s "$SIGNING_CERT_URL" -o signing_cert.pem
   ```

1. 從下載的 X.509 憑證中擷取**公有金鑰**。公有金鑰可讓您解密訊息的簽章並驗證其完整性。例如：

   ```
   openssl x509 -pubkey -noout -in signing_cert.pem > public_key.pem
   ```

1. 不同的訊息類型需要字串中不同的鍵值對才能簽署。識別**訊息類型** (`Type`Amazon SNS 訊息中的 欄位），以決定要包含哪些**鍵值對**：
   + **通知訊息** – 包括 `Message`、`MessageId`、 `Subject`（如果有）`TopicArn`、、 `Timestamp`和 `Type`。
   + **SubscriptionConfirmation** 或 **UnsubscribeConfirmation 訊息** – 包括 `Message`、`MessageId`、`SubscribeURL`、`Token`、`Timestamp`、 `TopicArn`和 `Type`。

1. Amazon SNS 需要字串簽署，以遵循嚴格的固定欄位順序進行驗證。**僅必須包含明確的必要欄位**，無法新增任何額外的欄位。只有在訊息中存在時`Subject`，才會包含選用欄位，例如 ，且必須出現在必要欄位順序所定義的確切位置。例如：

   ```
   KeyNameOne\nValueOne\nKeyNameTwo\nValueTwo
   ```
**重要**  
請勿在字串結尾新增換行字元。

1. 依位元組排序順序排列**索引鍵/值對** （按索引鍵名稱的字母）。

1. 使用下列格式範例建構**字串以簽署**：

   ```
   STRING_TO_SIGN=""
   for FIELD in "${FIELDS[@]}"; do
       VALUE=$(jq -r --arg field "$FIELD" '.[$field]' "$MESSAGE_FILE")
       STRING_TO_SIGN+="$FIELD\n$VALUE"
       # Append a newline after each field except the last one
       if [[ "$FIELD" != "Type" ]]; then
           STRING_TO_SIGN+="\n"
       fi
   done
   ```

   **通知訊息範例：**

   ```
   Message
   My Test Message
   MessageId
   4d4dc071-ddbf-465d-bba8-08f81c89da64
   Subject
   My subject
   Timestamp
   2019-01-31T04:37:04.321Z
   TopicArn
   arn:aws:sns:us-east-2:123456789012:s4-MySNSTopic-1G1WEFCOXTC0P
   Type
   Notification
   ```

   **SubscriptionConfirmation 範例：**

   ```
   Message
   Please confirm your subscription
   MessageId
   3d891288-136d-417f-bc05-901c108273ee
   SubscribeURL
   https://sns.us-east-2.amazonaws.com/...
   Timestamp
   2024-01-01T00:00:00.000Z
   Token
   abc123...
   TopicArn
   arn:aws:sns:us-east-2:123456789012:MyTopic
   Type
   SubscriptionConfirmation
   ```

1. 訊息中的 `Signature` 欄位以 Base64-encoded。您需要**解碼**它，將其**原始二進位格式**與**衍生的雜湊**進行比較。例如：

   ```
   SIGNATURE=$(jq -r '.Signature' "$MESSAGE_FILE")
   echo "$SIGNATURE" | base64 -d > signature.bin
   ```

1. 使用 `SignatureVersion` 欄位來選取雜湊演算法：
   + 對於 `SignatureVersion`**1**，請使用 **SHA1** （例如，`-sha1`)。
   + 對於 `SignatureVersion`**2**，請使用 **SHA256** （例如 `-sha256`)。

1. 若要確認 Amazon SNS 訊息的真實性，請產生建構字串的**雜湊**，並使用**公有金鑰**驗證簽章。

   ```
   openssl dgst -sha256 -verify public_key.pem -signature signature.bin <<< "$STRING_TO_SIGN"
   ```

   如果簽章有效，則輸出為 `Verified OK`。否則，輸出為 `Verification Failure`。

## 具有錯誤處理的範例指令碼
<a name="sns-verify-signature-of-message-example"></a>

下列範例指令碼會自動執行驗證程序：

```
#!/bin/bash

# Path to the local message file
MESSAGE_FILE="message.json"

# Extract the SigningCertURL and Signature from the message
SIGNING_CERT_URL=$(jq -r '.SigningCertURL' "$MESSAGE_FILE")
SIGNATURE=$(jq -r '.Signature' "$MESSAGE_FILE")

# Fetch the X.509 certificate
curl -s "$SIGNING_CERT_URL" -o signing_cert.pem

# Extract the public key from the certificate
openssl x509 -pubkey -noout -in signing_cert.pem > public_key.pem

# Define the fields to include in the string to sign
FIELDS=("Message" "MessageId" "Subject" "Timestamp" "TopicArn" "Type")

# Initialize the string to sign
STRING_TO_SIGN=""

# Iterate over the fields to construct the string to sign
for FIELD in "${FIELDS[@]}"; do
    VALUE=$(jq -r --arg field "$FIELD" '.[$field]' "$MESSAGE_FILE")
    STRING_TO_SIGN+="$FIELD\n$VALUE"
    # Append a newline after each field except the last one
    if [[ "$FIELD" != "Type" ]]; then
        STRING_TO_SIGN+="\n"
    fi
done

# Verify the signature
echo -e "$STRING_TO_SIGN" | openssl dgst -sha256 -verify public_key.pem -signature <(echo "$SIGNATURE" | base64 -d)
```