

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

# 使用公有金鑰執行離線操作
<a name="offline-public-key"></a>

在非對稱 KMS 金鑰中，私有金鑰是在 中建立 AWS KMS ，絕不會保持 AWS KMS 未加密狀態。若要使用私有金鑰，您必須呼叫 AWS KMS。您可以透過呼叫 AWS KMS API 操作 AWS KMS ，在 中使用公有金鑰。或者，您可以[下載公有金鑰](download-public-key.md)並共用以供 外部使用 AWS KMS。

您可以共用公有金鑰，讓其他人加密 以外的資料 AWS KMS ，您只能使用私有金鑰解密。或者，您可以允許其他人員在 AWS KMS 的外部驗證您使用私有金鑰產生的數位簽章。或者，與對等共用公有金鑰以衍生共用秘密。

當您在其中的非對稱 KMS 金鑰中使用公有金鑰時 AWS KMS，您會受益於每個 AWS KMS 操作一部分的身分驗證、授權和記錄。您也可以降低加密無法解密資料的風險。這些功能在 之外無效 AWS KMS。如需詳細資訊，請參閱[下載公開金鑰的特殊考量](#download-public-key-considerations)。

**提示**  
正在尋找資料金鑰或 SSH 金鑰？ 本主題介紹如何在不可匯出私有金鑰的 AWS Key Management Service中管理非對稱金鑰。如需有關可匯出資料金鑰對 (其中私有金鑰受對稱加密 KMS 金鑰的保護) 的資訊，請參閱 [GenerateDataKeyPair](https://docs.aws.amazon.com/kms/latest/APIReference/API_GenerateDataKeyPair.html)。如需下載與 Amazon EC2 執行個體相關聯的公有金鑰的說明，請參閱《[Amazon EC2 使用者指南](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/describe-keys.html#retrieving-the-public-key)》和 [Amazon EC2 使用者指南](https://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/describe-keys.html#retrieving-the-public-key)》中的*擷取公有金鑰*。

**Topics**
+ [下載公開金鑰的特殊考量](#download-public-key-considerations)
+ [下載公有金鑰](download-public-key.md)
+ [離線操作範例](offline-operations.md)

## 下載公開金鑰的特殊考量
<a name="download-public-key-considerations"></a>

為了保護您的 KMS 金鑰， AWS KMS 提供存取控制、已驗證加密和每個操作的詳細日誌。 AWS KMS 也可讓您暫時或永久避免使用 KMS 金鑰。最後， AWS KMS 操作旨在將加密無法解密資料的風險降至最低。當您在 外部使用下載的公有金鑰時，無法使用這些功能 AWS KMS。

**Authorization**  
控制在 內存取 KMS 金鑰的[金鑰政策和](key-policies.md) [IAM 政策](iam-policies.md) AWS KMS 不會影響在 外部執行的操作 AWS。任何可以取得公有金鑰的使用者都可以在 外部使用它， AWS KMS 即使他們沒有使用 KMS 金鑰加密資料或驗證簽章的許可。

**金鑰使用方式限制**  
金鑰使用限制在 之外無效 AWS KMS。如果您使用 的 KMS 金鑰呼叫 [Encrypt](https://docs.aws.amazon.com/kms/latest/APIReference/API_Encrypt.html) `KeyUsage` 操作`SIGN_VERIFY`， AWS KMS 操作會失敗。但是，如果您 AWS KMS 使用 `KeyUsage``SIGN_VERIFY`或 的 KMS 金鑰中的公有金鑰來加密 外部的資料`KEY_AGREEMENT`，則無法解密資料。

**演算法限制**  
對 AWS KMS 支援的加密和簽署演算法的限制在 之外無效 AWS KMS。如果您使用外部 KMS 金鑰的公有金鑰來加密資料 AWS KMS，並使用 AWS KMS 不支援的加密演算法，則無法解密資料。

**停用和刪除 KMS 金鑰**  
您可以採取防止在 密碼編譯操作中使用 KMS 金鑰的動作 AWS KMS ，不會阻止任何人在 外部使用公有金鑰 AWS KMS。例如，停用 KMS 金鑰、排程刪除 KMS 金鑰、刪除 KMS 金鑰，或是從 KMS 金鑰刪除金鑰材料等，對 AWS KMS外部的公有金鑰沒有任何影響。如果您刪除非對稱 KMS 金鑰，或刪除或遺失其金鑰材料，則您在 外部使用公有金鑰加密的資料將無法 AWS KMS 復原。

**日誌**  
AWS CloudTrail 記錄每個 AWS KMS 操作的日誌，包括請求、回應、日期、時間和授權使用者，不會記錄 外部公有金鑰的使用 AWS KMS。

**使用 SM2 金鑰對進行離線驗證 (僅限中國區域)**  
若要 AWS KMS 使用 SM2 公有金鑰驗證 外部的簽章，您必須指定辨別 ID。根據預設， AWS KMS 會使用 `1234567812345678`做為辨別 ID。如需更多資訊，請參閱[使用 SM2 金鑰對進行離線驗證 (僅限中國區域)。](offline-operations.md#key-spec-sm-offline-verification)

# 下載公有金鑰
<a name="download-public-key"></a>

您可以從 AWS KMS 主控台中的非對稱 KMS 金鑰對或使用 [GetPublicKey](https://docs.aws.amazon.com/kms/latest/APIReference/API_GetPublicKey.html) 操作下載公有金鑰。若要下載公有金鑰，您必須擁有非對稱 KMS 金鑰的`kms:GetPublicKey`許可。

 AWS KMS 傳回的公有金鑰是 DER 編碼的 X.509 公有金鑰，也稱為 `SubjectPublicKeyInfo`(SPKI)，如 [RFC 5280 ](https://datatracker.ietf.org/doc/html/rfc5280)所定義。當您使用 HTTP API 或 時 AWS CLI，值為 Base64-encoded。否則，它不是 Base64-encoded。

若要從非對稱 KMS 金鑰對下載公有金鑰，您需要 `kms:GetPublicKey` 許可。如需 AWS KMS 許可的詳細資訊，請參閱 [許可參考](kms-api-permissions-reference.md)。

## 使用 AWS KMS 主控台
<a name="download-public-key-console"></a>

您可以使用 從 中的非對稱 KMS 金鑰 AWS 管理主控台 檢視、複製和下載公有金鑰 AWS 帳戶。若要從不同 的非對稱 KMS 金鑰下載公有金鑰 AWS 帳戶，請使用 AWS KMS API。

1. 登入 AWS 管理主控台 並開啟位於 https：//[https://console.aws.amazon.com/kms](https://console.aws.amazon.com/kms) 的 AWS Key Management Service (AWS KMS) 主控台。

1. 若要變更 AWS 區域，請使用頁面右上角的區域選擇器。

1. 在導覽窗格中，選擇 **Customer managed keys** (客戶受管金鑰)。

1. 選擇非對稱 KMS 金鑰的別名或金鑰 ID。

1. 選擇 **Cryptographic configuration** (密碼編譯組態) 索引標籤。記錄 **Key spec** (金鑰規格)、**Key usage** (金鑰使用情形) 和 **Encryption algorithms** (加密演算法) 或 **Signing Algorithms** (簽署演算法) 欄位的值。您需要使用這些值，才能在 外部使用公有金鑰 AWS KMS。請務必在您共享公有金鑰時共享此資訊。

1. 選擇 **Public key (公有金鑰)** 標籤。

1. 如要將公有金鑰複製到您的剪貼簿，請使用 **Copy (複製)**。如要將公有金鑰下載至檔案，請選擇 **Download (下載)**。

## 使用 AWS KMS API
<a name="download-public-key-api"></a>

[GetPublicKey](https://docs.aws.amazon.com/kms/latest/APIReference/API_GetPublicKey.html) 操作會傳回非對稱 KMS 金鑰中的公有金鑰。它也會傳回您在 外部正確使用公有金鑰所需的重要資訊 AWS KMS，包括金鑰用量和加密演算法。請務必儲存這些值，並在您共享公有金鑰時也共享這些資訊。

本節中的範例使用 [AWS Command Line Interface (AWS CLI)](https://aws.amazon.com/cli/)，但您可以使用任何支援的程式設計語言。

若要指定 KMS 金鑰，請使用它的[金鑰 ID](concepts.md#key-id-key-id)、[金鑰 ARN](concepts.md#key-id-key-ARN)、[別名名稱](concepts.md#key-id-alias-name)或[別名 ARN](concepts.md#key-id-alias-ARN)。使用別名時，請加上 **alias/** 字首。若要在不同的 中指定 KMS 金鑰 AWS 帳戶，您必須使用其金鑰 ARN 或別名 ARN。

執行此命令前，請將範例別名替換成 KMS 金鑰的有效識別符。如要執行此命令，您必須擁有 KMS 金鑰的 `kms:GetPublicKey` 許可。

```
$ aws kms get-public-key --key-id alias/example_RSA_3072

{
    "KeySpec": "RSA_3072",
    "KeyId": "arn:aws:kms:us-west-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab",
    "KeyUsage": "ENCRYPT_DECRYPT",
    "EncryptionAlgorithms": [
        "RSAES_OAEP_SHA_1",
        "RSAES_OAEP_SHA_256"
    ],
    "PublicKey": "MIIBojANBgkqhkiG..."
}
```

# 離線操作範例
<a name="offline-operations"></a>

[下載非對稱 KMS 金鑰對的公有金鑰](download-public-key.md)之後，您可以與他人共用，並使用它來執行離線操作。

AWS CloudTrail 記錄每個 AWS KMS 操作的日誌，包括請求、回應、日期、時間和授權使用者，不會記錄 外部公有金鑰的使用 AWS KMS。

本主題提供範例離線操作，以及工具 AWS KMS 提供的詳細資訊，讓您更輕鬆地進行離線操作。

**Topics**
+ [離線衍生共用秘密](#key-spec-ecc-offline)
+ [使用 ML-DSA 金鑰對進行離線驗證](#mldsa-offline-verification)
+ [使用 SM2 金鑰對進行離線驗證 (僅限中國區域)](#key-spec-sm-offline-verification)

## 離線衍生共用秘密
<a name="key-spec-ecc-offline"></a>

您可以[下載 ECC 金鑰對的公有金鑰](download-public-key.md)，用於離線操作，也就是 外部的操作 AWS KMS。

下列 [OpenSSL](https://openssl.org/) 演練示範一種在外部使用 ECC KMS 金鑰對的 AWS KMS 公有金鑰和使用 OpenSSL 建立的私有金鑰衍生共用秘密的方法。

1. 在 OpenSSL 中建立 ECC 金鑰對，並準備與 搭配使用 AWS KMS。

   ```
   // Create an ECC key pair in OpenSSL and save the private key in openssl_ecc_key_priv.pem
   export OPENSSL_CURVE_NAME="P-256"
   export KMS_CURVE_NAME="ECC_NIST_P256"
   
   export OPENSSL_KEY1_PRIV_PEM="openssl_ecc_key1_priv.pem"
   openssl ecparam -name ${OPENSSL_CURVE_NAME} -genkey -out ${OPENSSL_KEY1_PRIV_PEM}                    
                       
   // Derive the public key from the private key                    
   export OPENSSL_KEY1_PUB_PEM="openssl_ecc_key1_pub.pem"
   openssl ec -in ${OPENSSL_KEY1_PRIV_PEM} -pubout -outform pem \
       -out ${OPENSSL_KEY1_PUB_PEM}                    
                       
   // View the PEM file containing the public key and extract the public key as a 
   // Base64 encoded string into OPENSSL_KEY1_PUB_BASE64 for use with AWS KMS
   export OPENSSL_KEY1_PUB_BASE64=`cat ${OPENSSL_KEY1_PUB_PEM} | \
       tee /dev/stderr | grep -v "PUBLIC KEY" | tr -d "\n"`
   ```

1. 在 中建立 ECC 金鑰協議金鑰對， AWS KMS 並準備與 OpenSSL 搭配使用。

   ```
   // Create a KMS key on the same curve as the key pair from step 1 
   // with a key usage of KEY_AGREEMENT
   // Save its ARN in KMS_KEY1_ARN.
   export KMS_KEY1_ARN=`aws kms create-key --key-spec ${KMS_CURVE_NAME} \
       --key-usage KEY_AGREEMENT | tee /dev/stderr | jq -r .KeyMetadata.Arn`
   
   // Download the public key and save the Base64-encoded version in KMS_KEY1_PUB_BASE64        
   export KMS_KEY1_PUB_BASE64=`aws kms get-public-key --key-id ${KMS_KEY1_ARN} | \
       tee /dev/stderr | jq -r .PublicKey`                    
                       
   // Create a PEM file for the public KMS key for use with OpenSSL   
   export KMS_KEY1_PUB_PEM="aws_kms_ecdh_key1_pub.pem"
   echo "-----BEGIN PUBLIC KEY-----" > ${KMS_KEY1_PUB_PEM}
   echo ${KMS_KEY1_PUB_BASE64} | fold -w 64 >> ${KMS_KEY1_PUB_PEM}
   echo "-----END PUBLIC KEY-----" >> ${KMS_KEY1_PUB_PEM}
   ```

1. 使用 OpenSSL 中的私有金鑰和公有 KMS 金鑰，衍生 OpenSSL 中的共用秘密。

   ```
   export OPENSSL_SHARED_SECRET1_BIN="openssl_shared_secret1.bin"
   openssl pkeyutl -derive -inkey ${OPENSSL_KEY1_PRIV_PEM} \
       -peerkey ${KMS_KEY1_PUB_PEM} -out ${OPENSSL_SHARED_SECRET1_BIN}
   ```

## 使用 ML-DSA 金鑰對進行離線驗證
<a name="mldsa-offline-verification"></a>

AWS KMS 支援 ML-DSA 簽署的對沖變體，如[聯邦資訊處理標準 (FIPS) 204 標準](https://csrc.nist.gov/pubs/fips/204/final)第 3.4 節所述，適用於高達 4 KB 位元組的訊息。

若要簽署大於 4 KB 的訊息，請在 外部執行訊息預先處理步驟 AWS KMS。此雜湊步驟會建立 64 位元組訊息代表 μ，如 NIST FIPS 204 第 6.2 節所定義。

AWS KMS 對於大於 `EXTERNAL_MU` 4 KB 的訊息， 有一個名為 的訊息類型。當您使用此功能而非`RAW`訊息類型時，請執行下列動作 AWS KMS：
+ 假設您已執行雜湊步驟
+ 略過其內部雜湊程序
+ 適用於任何大小的訊息

驗證訊息時，您使用的方法取決於外部系統或程式庫的大小限制，以及是否支援 64 位元組訊息代表 μ：
+ 如果訊息小於大小限制，請使用 `RAW` 訊息類型。
+ 如果訊息大於大小限制，請在外部系統中使用 代表 μ。

下列各節示範如何使用 簽署訊息 AWS KMS ，並使用 OpenSSL 驗證訊息。我們提供低於和超過 4 KB 訊息大小限制的訊息範例 AWS KMS。OpenSSL 不會對訊息大小施加限制以進行驗證。

對於這兩個範例，請先從 取得公有金鑰 AWS KMS。使用下列 AWS CLI 命令：

```
aws kms get-public-key \
    --key-id _<1234abcd-12ab-34cd-56ef-1234567890ab>_ \
    --output text \
    --query PublicKey | base64 --decode > public_key.der
```

### 訊息大小小於 4KB
<a name="mldsa-offline-verification-less-than-4KB"></a>

對於小於 4 KB 的訊息，請使用 `RAW` 訊息類型 AWS KMS。雖然您可以使用 `EXTERNAL_MU`，但大小限制內的訊息並不需要。

使用下列 AWS CLI 命令來簽署訊息：

```
aws kms sign \
    --key-id _<1234abcd-12ab-34cd-56ef-1234567890ab>_ \
    --message 'your message' \
    --message-type RAW \
    --signing-algorithm ML_DSA_SHAKE_256 \
    --output text \
    --query Signature | base64 --decode > ExampleSignature.bin
```

若要使用 OpenSSL 驗證此訊息，請使用下列命令：

```
echo -n 'your message' | ./openssl dgst -verify public_key.der -signature ExampleSignature.bin
```

### 訊息大小超過 4KB
<a name="mldsa-offline-verification-more-than-4KB"></a>

若要簽署大於 4KB 的訊息，請使用 `EXTERNAL_MU` 訊息類型。使用 時`EXTERNAL_MU`，您會預先將訊息置於 NIST FIPS 204 第 6.2 節所定義的 64 位元組代表 μ 外部，並將其傳遞給簽署或驗證操作。請注意，這與 NIST FIPS 204 第 5.4 節中定義的「雜湊前 MLDSA」或 HashML-DSA 不同。

1. 首先，建構訊息字首。字首包含網域分隔符號、任何內容的長度，以及內容。網域分隔符號和內容長度的預設值為零。

1. 在訊息前面加上訊息字首。

1. 使用 SHAKE256 雜湊公有金鑰，並在步驟 2 的結果前面加上。

1. 最後，雜湊步驟 3 的結果以產生 64 位元組的 `EXTERNAL_MU`。

下列範例使用 OpenSSL 3.5 來建構 `EXTERNAL_MU`：

```
{
    openssl asn1parse -inform DER -in public_key.der -strparse 17 -noout -out - 2>/dev/null |
    openssl dgst -provider default -shake256 -xoflen 64 -binary;
    printf '\x00\x00';
    echo -n "your message"
} | openssl dgst -provider default -shake256 -xoflen 64 -binary > mu.bin
```

建立`mu.bin`檔案之後，請使用下列命令呼叫 AWS KMS API 來簽署訊息：

```
aws kms sign \
    --key-id _<1234abcd-12ab-34cd-56ef-1234567890ab>_ \
    --message fileb://mu.bin \
    --message-type EXTERNAL_MU \
    --signing-algorithm ML_DSA_SHAKE_256 \
    --output text \
    --query Signature | base64 --decode > ExampleSignature.bin
```

產生的簽章與原始訊息上的`RAW`簽章相同。您可以使用相同的 OpenSSL 3.5 命令來驗證訊息：

```
echo -n 'your message' | ./openssl dgst -verify public_key.der -signature ExampleSignature.bin
```

## 使用 SM2 金鑰對進行離線驗證 (僅限中國區域)
<a name="key-spec-sm-offline-verification"></a>

若要 AWS KMS 使用 SM2 公有金鑰驗證 外部的簽章，您必須指定辨別 ID。當您將原始訊息 傳遞[https://docs.aws.amazon.com/kms/latest/APIReference/API_Sign.html#KMS-Sign-request-MessageType](https://docs.aws.amazon.com/kms/latest/APIReference/API_Sign.html#KMS-Sign-request-MessageType)至 [Sign](https://docs.aws.amazon.com/kms/latest/APIReference/API_Sign.html) API 時， AWS KMS 會使用 OSCCA 在 GM/T 0009-2012 中`1234567812345678`定義的預設辨別 ID 。您無法在 AWS KMS中指定自己的辦別 ID。

不過，如果您要在 外部產生訊息摘要 AWS，您可以指定自己的辨別 ID，然後將訊息摘要 傳遞[https://docs.aws.amazon.com/kms/latest/APIReference/API_Sign.html#API_Sign_RequestSyntax](https://docs.aws.amazon.com/kms/latest/APIReference/API_Sign.html#API_Sign_RequestSyntax)給 AWS KMS 以簽署。若要執行此操作，請變更 `SM2OfflineOperationHelper` 類別中的 `DEFAULT_DISTINGUISHING_ID` 值。您指定的辨別 ID 可以是最長 8,192 個字元的任何字串。 AWS KMS 簽署訊息摘要後，您需要訊息摘要或訊息，以及用來計算摘要以離線驗證摘要的辨別 ID。

**重要**  
`SM2OfflineOperationHelper` 參考程式碼設計為與 [Bouncy Castle](https://www.bouncycastle.org/documentation/documentation-java/) 1.68 版相容。如需其他版本的幫助，請聯絡 [bouncycastle.org](https://www.bouncycastle.org)。

### `SM2OfflineOperationHelper` 類別
<a name="key-spec-sm-offline-helper"></a>

為了協助您使用 SM2 金鑰進行離線操作，適用於 Java 的 `SM2OfflineOperationHelper`類別具有可為您執行任務的方法。您可以使用此輔助程式類別作為針對其他密碼提供者的模型。

在其中 AWS KMS，會自動進行原始加密文字轉換和 SM2DSA 訊息摘要計算。並非所有加密提供者都以相同的方式實施 SM2。有些程式庫，例如 [OpenSSL](https://openssl.org/) 1.1.1 版和更新版本，會自動執行這些動作。 在測試 OpenSSL 3.0 版時 AWS KMS 確認此行為。使用以下 `SM2OfflineOperationHelper` 類別和像是 [Bouncy Castle](https://www.bouncycastle.org/java.html) 之類的程式庫，則需要您手動執行這些轉換和計算。

所以 `SM2OfflineOperationHelper` 類別提供了進行以下離線操作的方法：
+   
**訊息摘要計算**  
若要產生離線訊息摘要，供您用於離線驗證，或您可以傳遞給 AWS KMS 進行簽署，請使用 `calculateSM2Digest`方法。`calculateSM2Digest` 會使用 SM3 雜湊演算法產生訊息摘要。[GetPublicKey](https://docs.aws.amazon.com/kms/latest/APIReference/API_GetPublicKey.html) API 會傳回二進位格式的公有金鑰。您必須將二進位金鑰剖析為 Java PublicKey。提供剖析好的公有金鑰以及訊息。該方法會自動將您的訊息與預設的辨別 ID (`1234567812345678`) 相結合，但您可以藉由變更 `DEFAULT_DISTINGUISHING_ID` 值來設置自己的辨別 ID。
+   
**確認**  
若要離線驗證簽署，請使用 `offlineSM2DSAVerify` 方法。`offlineSM2DSAVerify` 方法使用從指定辨別 ID 計算出的訊息摘要，以及您提供的原始訊息來驗證數位簽署。[GetPublicKey](https://docs.aws.amazon.com/kms/latest/APIReference/API_GetPublicKey.html) API 會傳回二進位格式的公有金鑰。您必須將二進位金鑰剖析為 Java PublicKey。提供已剖析的公開金鑰，以及您要驗證的原始訊息和簽章。如需詳細資訊，請參閱[使用 SM2 金鑰對進行離線驗證](#key-spec-sm-offline-verification)。
+   
**加密**  
若要離線加密明文，請使用 `offlineSM2PKEEncrypt` 方法。此方法可確保加密文字的格式 AWS KMS 可以解密。`offlineSM2PKEEncrypt` 方法加密明文，然後將由 SM2PKE 產生的原始密文轉換為 ASN.1 格式。[GetPublicKey](https://docs.aws.amazon.com/kms/latest/APIReference/API_GetPublicKey.html) API 會傳回二進位格式的公有金鑰。您必須將二進位金鑰剖析為 Java PublicKey。提供剖析好的公有金鑰以及以您要加密的明文。  
如果您不確定是否需要執行轉換，請使用以下 OpenSSL 操作來測試您的密文格式。如果操作失敗，則需要將密文轉換為 ASN.1 格式。  

  ```
  openssl asn1parse -inform DER -in ciphertext.der
  ```

根據預設，當產生用於 SM2DSA 操作的訊息摘要時，`SM2OfflineOperationHelper` 類別使用預設的辨別 ID，即 `1234567812345678`。

```
package com.amazon.kms.utils;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;

import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.jce.interfaces.ECPublicKey;

import java.util.Arrays;

import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.params.ParametersWithID;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;

public class SM2OfflineOperationHelper {
    // You can change the DEFAULT_DISTINGUISHING_ID value to set your own distinguishing ID,
    // the DEFAULT_DISTINGUISHING_ID can be any string up to 8,192 characters long.
    private static final byte[] DEFAULT_DISTINGUISHING_ID = "1234567812345678".getBytes(StandardCharsets.UTF_8);
    private static final X9ECParameters SM2_X9EC_PARAMETERS = GMNamedCurves.getByName("sm2p256v1");

    // ***calculateSM2Digest***
    // Calculate message digest
    public static byte[] calculateSM2Digest(final PublicKey publicKey, final byte[] message) throws
            NoSuchProviderException, NoSuchAlgorithmException {
        final ECPublicKey ecPublicKey = (ECPublicKey) publicKey;

        // Generate SM3 hash of default distinguishing ID, 1234567812345678
        final int entlenA = DEFAULT_DISTINGUISHING_ID.length * 8;
        final byte [] entla = new byte[] { (byte) (entlenA & 0xFF00), (byte) (entlenA & 0x00FF) };
        final byte [] a = SM2_X9EC_PARAMETERS.getCurve().getA().getEncoded();
        final byte [] b = SM2_X9EC_PARAMETERS.getCurve().getB().getEncoded();
        final byte [] xg = SM2_X9EC_PARAMETERS.getG().getXCoord().getEncoded();
        final byte [] yg = SM2_X9EC_PARAMETERS.getG().getYCoord().getEncoded();
        final byte[] xa = ecPublicKey.getQ().getXCoord().getEncoded();
        final byte[] ya = ecPublicKey.getQ().getYCoord().getEncoded();
        final byte[] za = MessageDigest.getInstance("SM3", "BC")
                .digest(ByteBuffer.allocate(entla.length + DEFAULT_DISTINGUISHING_ID.length + a.length + b.length + xg.length + yg.length +
                        xa.length + ya.length).put(entla).put(DEFAULT_DISTINGUISHING_ID).put(a).put(b).put(xg).put(yg).put(xa).put(ya)
                        .array());

        // Combine hashed distinguishing ID with original message to generate final digest
        return MessageDigest.getInstance("SM3", "BC")
                .digest(ByteBuffer.allocate(za.length + message.length).put(za).put(message)
                        .array());
    }

    // ***offlineSM2DSAVerify***
    // Verify digital signature with SM2 public key
    public static boolean offlineSM2DSAVerify(final PublicKey publicKey, final byte [] message,
            final byte [] signature) throws InvalidKeyException {
        final SM2Signer signer = new SM2Signer();
        CipherParameters cipherParameters = ECUtil.generatePublicKeyParameter(publicKey);
        cipherParameters = new ParametersWithID(cipherParameters, DEFAULT_DISTINGUISHING_ID);
        signer.init(false, cipherParameters);
        signer.update(message, 0, message.length);
        return signer.verifySignature(signature);
    }

    // ***offlineSM2PKEEncrypt***
    // Encrypt data with SM2 public key
    public static byte[] offlineSM2PKEEncrypt(final PublicKey publicKey, final byte [] plaintext) throws
            NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException,
            BadPaddingException, IllegalBlockSizeException, IOException {
        final Cipher sm2Cipher = Cipher.getInstance("SM2", "BC");
        sm2Cipher.init(Cipher.ENCRYPT_MODE, publicKey);

        // By default, Bouncy Castle returns raw ciphertext in the c1c2c3 format
        final byte [] cipherText = sm2Cipher.doFinal(plaintext);

        // Convert the raw ciphertext to the ASN.1 format before passing it to AWS KMS
        final ASN1EncodableVector asn1EncodableVector = new ASN1EncodableVector();
        final int coordinateLength = (SM2_X9EC_PARAMETERS.getCurve().getFieldSize() + 7) / 8 * 2 + 1;
        final int sm3HashLength = 32;
        final int xCoordinateInCipherText = 33;
        final int yCoordinateInCipherText = 65;
        byte[] coords = new byte[coordinateLength];
        byte[] sm3Hash = new byte[sm3HashLength];
        byte[] remainingCipherText = new byte[cipherText.length - coordinateLength - sm3HashLength];

        // Split components out of the ciphertext
        System.arraycopy(cipherText, 0, coords, 0, coordinateLength);
        System.arraycopy(cipherText, cipherText.length - sm3HashLength, sm3Hash, 0, sm3HashLength);
        System.arraycopy(cipherText, coordinateLength, remainingCipherText, 0,cipherText.length - coordinateLength - sm3HashLength);

        // Build standard SM2PKE ASN.1 ciphertext vector
        asn1EncodableVector.add(new ASN1Integer(new BigInteger(1, Arrays.copyOfRange(coords, 1, xCoordinateInCipherText))));
        asn1EncodableVector.add(new ASN1Integer(new BigInteger(1, Arrays.copyOfRange(coords, xCoordinateInCipherText, yCoordinateInCipherText))));
        asn1EncodableVector.add(new DEROctetString(sm3Hash));
        asn1EncodableVector.add(new DEROctetString(remainingCipherText));

        return new DERSequence(asn1EncodableVector).getEncoded("DER");
    }
}
```