

As traduções são geradas por tradução automática. Em caso de conflito entre o conteúdo da tradução e da versão original em inglês, a versão em inglês prevalecerá.

# Realizar operações offline com chaves públicas
<a name="offline-public-key"></a>

Em uma chave KMS assimétrica, a chave privada é criada AWS KMS e nunca sai sem criptografia. AWS KMS Para usar a chave privada, você deve ligar AWS KMS. Você pode usar a chave pública interna AWS KMS chamando as operações AWS KMS da API. Ou você pode [baixar a chave pública](download-public-key.md) e compartilhá-la para uso fora do AWS KMS.

Você pode compartilhar uma chave pública para permitir que outras pessoas criptografem dados além dos AWS KMS que você pode descriptografar somente com sua chave privada. Ou para permitir que outras pessoas verifiquem uma assinatura digital fora do AWS KMS que você gerou com a chave privada. Ou ainda para compartilhar sua chave pública com um par para obter um segredo compartilhado.

Ao usar a chave pública em sua chave KMS assimétrica AWS KMS, você se beneficia da autenticação, autorização e registro que fazem parte de cada operação. AWS KMS Você também reduz o risco de criptografar dados que não podem ser descriptografados. Esses recursos não são eficazes fora do AWS KMS. Para obter detalhes, consulte [Considerações especiais sobre o download de chaves públicas](#download-public-key-considerations).

**dica**  
Está procurando por chaves de dados ou chaves SSH? Este tópico explica como gerenciar chaves assimétricas no AWS Key Management Service, onde a chave privada não é exportável. Para pares de chaves de dados exportáveis em que a chave privada é protegida por uma chave KMS de criptografia simétrica, consulte. [GenerateDataKeyPair](https://docs.aws.amazon.com/kms/latest/APIReference/API_GenerateDataKeyPair.html) Para obter ajuda com o download da chave pública associada a uma instância do Amazon EC2, consulte *Recuperação da chave pública* no [Guia do usuário do Amazon EC2](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/describe-keys.html#retrieving-the-public-key) e no [Guia do usuário do Amazon EC2](https://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/describe-keys.html#retrieving-the-public-key).

**Topics**
+ [Considerações especiais sobre o download de chaves públicas](#download-public-key-considerations)
+ [Fazer download de chave pública](download-public-key.md)
+ [Exemplo de operações offline](offline-operations.md)

## Considerações especiais sobre o download de chaves públicas
<a name="download-public-key-considerations"></a>

Para proteger suas chaves KMS, AWS KMS fornece controles de acesso, criptografia autenticada e registros detalhados de cada operação. AWS KMS também permite impedir o uso de chaves KMS, temporária ou permanentemente. Por fim, AWS KMS as operações são projetadas para minimizar o risco de criptografar dados que não podem ser descriptografados. Esses recursos não estão disponíveis quando você usa chaves públicas baixadas fora do AWS KMS. 

**Autorização**  
[Políticas-chave e políticas](key-policies.md) [do IAM](iam-policies.md) que controlam o acesso à chave KMS interna AWS KMS não afetam as operações realizadas fora da AWS. Qualquer usuário que possa obter a chave pública pode usá-la externamente, AWS KMS mesmo que não tenha permissão para criptografar dados ou verificar assinaturas com a chave KMS.

**Restrições de uso da chave**  
As principais restrições de uso não são efetivas fora do AWS KMS. Se você chamar a operação [Criptografar](https://docs.aws.amazon.com/kms/latest/APIReference/API_Encrypt.html) com uma chave KMS que tenha um `KeyUsage` of`SIGN_VERIFY`, a AWS KMS operação falhará. Mas se você criptografar dados externos AWS KMS com uma chave pública de uma chave KMS com um `KeyUsage` de `SIGN_VERIFY` ou`KEY_AGREEMENT`, os dados não poderão ser descriptografados.

**Restrições de algoritmo**  
Restrições nos algoritmos de criptografia e assinatura AWS KMS compatíveis não são efetivas fora do AWS KMS. Se você criptografar dados com a chave pública de uma chave KMS externa e usar um algoritmo de AWS KMS criptografia incompatível, os dados AWS KMS não poderão ser descriptografados. 

**Desabilitar e excluir chaves do KMS**  
As ações que você pode tomar para impedir o uso da chave KMS em uma operação criptográfica interna AWS KMS não impedem que ninguém use a chave pública fora dela. AWS KMS Por exemplo, desabilitar uma chave do KMS, programar a exclusão de uma chave do KMS, excluir uma chave do KMS ou excluir o material de chave de uma chave do KMS não têm efeito em uma chave pública fora do AWS KMS. Se você excluir uma chave KMS assimétrica ou excluir ou perder seu material de chaves, os dados que você criptografa com uma chave pública externa não poderão ser recuperados. AWS KMS 

**Registro em log**  
AWS CloudTrail registros que registram todas as AWS KMS operações, incluindo solicitação, resposta, data, hora e usuário autorizado, não registram o uso da chave pública fora do AWS KMS.

**Verificação off-line com pares de SM2 chaves (somente regiões da China)**  
Para verificar uma assinatura externa AWS KMS com uma chave SM2 pública, você deve especificar a ID distintiva. Por padrão, AWS KMS usa `1234567812345678` como ID distintiva. Para obter mais informações, consulte [Verificação off-line com pares de SM2 chaves (somente regiões da China).](offline-operations.md#key-spec-sm-offline-verification)

# Fazer download de chave pública
<a name="download-public-key"></a>

Você pode baixar a chave pública de um par de chaves KMS assimétrico no AWS KMS console ou usando a operação. [GetPublicKey](https://docs.aws.amazon.com/kms/latest/APIReference/API_GetPublicKey.html) Para baixar a chave pública, é necessário ter a permissão `kms:GetPublicKey` na chave do KMS assimétrica.

[A chave pública que AWS KMS retorna é uma chave pública X.509 codificada em DER, também conhecida como `SubjectPublicKeyInfo` (SPKI), conforme definido na RFC 5280.](https://datatracker.ietf.org/doc/html/rfc5280) Quando você usa a API HTTP ou a AWS CLI, o valor é codificado em Base64. Caso contrário, ele não é codificado em base64.

Para baixar a chave pública de um par de chave do KMS assimétrica, é necessário ter a permissão `kms:GetPublicKey`. Para obter mais informações sobre AWS KMS permissões, consulte [Referência de permissões](kms-api-permissions-reference.md) o.

## Usando o AWS KMS console
<a name="download-public-key-console"></a>

Você pode usar o Console de gerenciamento da AWS para visualizar, copiar e baixar a chave pública de uma chave KMS assimétrica em seu. Conta da AWS Para baixar a chave pública de uma chave KMS assimétrica em diferente Conta da AWS, use a API. AWS KMS 

1. Faça login no console Console de gerenciamento da AWS e abra o AWS Key Management Service (AWS KMS) em [https://console.aws.amazon.com/kms](https://console.aws.amazon.com/kms).

1. Para alterar o Região da AWS, use o seletor de região no canto superior direito da página.

1. No painel de navegação, escolha **Chaves gerenciadas pelo cliente**.

1. Escolha o alias ou o ID de chave de uma chave do KMS assimétrica.

1. Selecione a guia **Cryptographic configuration** (Configuração criptográfica). Registre os valores dos campos **Especificação de chave**, **Uso de chave** e **Algoritmos de criptografia** ou **Algoritmos de assinatura**. Você precisará usar esses valores para usar a chave pública fora do AWS KMS. Compartilhe essas informações ao compartilhar a chave pública.

1. Selecione a guia **Public key (Chave pública)**.

1. Para copiar a chave pública para a área de transferência, selecione **Copy (Copiar)**. Para fazer download da chave pública em um arquivo, selecione **Download (Fazer download)**.

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

A [GetPublicKey](https://docs.aws.amazon.com/kms/latest/APIReference/API_GetPublicKey.html)operação retorna a chave pública em uma chave KMS assimétrica. Ele também retorna informações críticas das quais você precisa usar corretamente a chave pública fora dela AWS KMS, incluindo o uso da chave e os algoritmos de criptografia. Salve esses valores e compartilhe-os sempre que compartilhar a chave pública.

Os exemplos nesta seção usam a [AWS Command Line Interface (AWS CLI)](https://aws.amazon.com/cli/), mas você pode usar qualquer linguagem de programação compatível. 

Para especificar uma chave do KMS, use seu [ID de chave](concepts.md#key-id-key-id), [ARN de chave](concepts.md#key-id-key-ARN), [nome de alias](concepts.md#key-id-alias-name) ou [ARN de alias](concepts.md#key-id-alias-ARN). Ao usar um nome de alias, use **alias/** como prefixo dele. Para especificar uma chave KMS em outra Conta da AWS, você deve usar o ARN da chave ou o alias ARN.

Antes de executar esse comando, substitua o nome de alias de exemplo por um identificador válido para a chave do KMS. Para executar esse comando, é necessário ter as permissões `kms:GetPublicKey` na chave do KMS.

```
$ 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..."
}
```

# Exemplo de operações offline
<a name="offline-operations"></a>

Após [baixar a chave pública](download-public-key.md) do seu par de chaves do KMS assimétricas, você poderá compartilhá-lo com outras pessoas e usá-lo para realizar operações offline.

AWS CloudTrail registros que registram todas as AWS KMS operações, incluindo solicitação, resposta, data, hora e usuário autorizado, não registram o uso da chave pública fora do AWS KMS.

Este tópico fornece exemplos de operações off-line e detalhes que as ferramentas AWS KMS fornecem para facilitar as operações off-line.

**Topics**
+ [Derivar segredos compartilhados offline](#key-spec-ecc-offline)
+ [Verificação off-line com pares de chaves ML-DSA](#mldsa-offline-verification)
+ [Verificação off-line com pares de SM2 chaves (somente regiões da China)](#key-spec-sm-offline-verification)

## Derivar segredos compartilhados offline
<a name="key-spec-ecc-offline"></a>

Você pode [baixar a chave pública](download-public-key.md) do seu par de chaves ECC para uso em operações offline, ou seja, operações fora do AWS KMS.

O seguinte passo a passo do [OpenSSL](https://openssl.org/) demonstra um método de derivar um segredo compartilhado fora do uso da chave pública de um par AWS KMS de chaves ECC KMS e de uma chave privada criada com o OpenSSL.

1. Crie um par de chaves ECC no OpenSSL e prepare-o para uso com o. 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. Crie um par de chaves de acordo de chave ECC AWS KMS e prepare-o para uso com o 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. Derive o segredo compartilhado no OpenSSL usando a chave privada no OpenSSL e a chave pública do KMS.

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

## Verificação off-line com pares de chaves ML-DSA
<a name="mldsa-offline-verification"></a>

AWS KMS suporta uma variante protegida da assinatura ML-DSA, conforme descrito na seção 3.4 do [padrão Federal Information Processing Standards (FIPS) 204](https://csrc.nist.gov/pubs/fips/204/final) para mensagens de até 4 KB bytes.

Para assinar mensagens maiores que 4 KB, você executa a etapa de pré-processamento de mensagens fora do AWS KMS. Essa etapa de hash cria um μ representativo de mensagem de 64 bytes, conforme definido no NIST FIPS 204, seção 6.2.

AWS KMS tem um tipo de mensagem chamado `EXTERNAL_MU` para mensagens maiores que 4 KB. Quando você usa isso em vez do tipo de `RAW` mensagem, AWS KMS:
+ Presume que você já tenha executado a etapa de hash
+ Ignora seu próprio processo interno de hash
+ Funciona com mensagens de qualquer tamanho

Quando você verifica uma mensagem, o método usado depende da restrição de tamanho da biblioteca ou sistema externo e ser ou não compatível com o μ representativo de mensagem de 64 bytes:
+ Se a mensagem for menor que a restrição de tamanho, use o tipo de mensagem `RAW`.
+ Se a mensagem for maior que a restrição de tamanho, use o μ representativo no sistema externo.

As seções a seguir demonstram como assinar AWS KMS e verificar mensagens usando o OpenSSL. Fornecemos exemplos para mensagens abaixo e acima do limite de tamanho de mensagem de 4 KB imposto pelo AWS KMS. O OpenSSL não impõe um limite no tamanho da mensagem para verificação.

Para ambos os exemplos, primeiro obtenha a chave pública de AWS KMS. Use o seguinte comando AWS CLI :

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

### Tamanho de mensagem menor que 4 KB
<a name="mldsa-offline-verification-less-than-4KB"></a>

Para mensagens com menos de 4 KB, use o tipo de `RAW` mensagem com AWS KMS. Embora você possa usar `EXTERNAL_MU`, isso não é necessário para mensagens dentro do limite de tamanho.

Use o AWS CLI comando a seguir para assinar a mensagem:

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

Para verificar essa mensagem usando o OpenSSL, use o seguinte comando:

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

### Tamanho de mensagem maior que 4 KB
<a name="mldsa-offline-verification-more-than-4KB"></a>

Para assinar mensagens maiores que 4 KB, use o tipo de mensagem `EXTERNAL_MU`. Quando usa `EXTERNAL_MU`, você faz externamente o pré-hash da mensagem para um μ representativo de 64 bytes conforme definido no NIST FIPS 204, seção 6.2 e a encaminha para as operações de assinatura ou verificação. Observe que isso é diferente de "MLDSA pré-hash" ou HashML-DSA definido no NIST FIPS 204, seção 5.4. 

1. Primeiro, crie um prefixo de mensagem. O prefixo contém um separador de domínio, o tamanho de qualquer contexto e o contexto. O padrão para o separador de domínio e o comprimento de contexto é zero.

1. Anexe o prefixo à mensagem.

1. Use SHAKE256 para fazer o hash da chave pública e anexá-la ao resultado da etapa 2.

1. Por fim, faça um hash no resultado da etapa 3 para gerar o `EXTERNAL_MU` de 64 bytes.

O exemplo a seguir usa o OpenSSL 3.5 para criar o: `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
```

Depois de criar o `mu.bin` arquivo, chame a AWS KMS API com o seguinte comando para assinar a mensagem:

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

A assinatura resultante é igual à assinatura `RAW` da mensagem original. Você pode usar o mesmo comando do OpenSSL 3.5 para verificar a mensagem:

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

## Verificação off-line com pares de SM2 chaves (somente regiões da China)
<a name="key-spec-sm-offline-verification"></a>

Para verificar uma assinatura externa AWS KMS com uma chave SM2 pública, você deve especificar a ID distintiva. Quando você passa uma mensagem bruta, [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), para a API [Sign](https://docs.aws.amazon.com/kms/latest/APIReference/API_Sign.html), AWS KMS usa o ID distintivo padrão`1234567812345678`, definido pela OSCCA em 0009-2012. GM/T Você não pode especificar seu próprio ID de distinção no AWS KMS.

No entanto, se você estiver gerando um resumo da mensagem fora do AWS, poderá especificar seu próprio ID distintivo e, em seguida, passar o resumo da mensagem, [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), para AWS KMS assinar. Para isso, altere o valor de `DEFAULT_DISTINGUISHING_ID` na classe `SM2OfflineOperationHelper`. O ID distintivo especificado pode ser qualquer string de até 8.192 caracteres. Depois de AWS KMS assinar o resumo da mensagem, você precisa do resumo da mensagem ou da mensagem e do ID distintivo usado para computar o resumo para verificá-lo offline.

**Importante**  
O código de referência `SM2OfflineOperationHelper` foi projetado para ser compatível com o [Bouncy Castle](https://www.bouncycastle.org/documentation/documentation-java/) versão 1.68. Para obter ajuda com outras versões, entre em contato com [bouncycastle.org](https://www.bouncycastle.org).

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

Para ajudá-lo com operações off-line com SM2 chaves, a `SM2OfflineOperationHelper` classe para Java tem métodos que executam as tarefas para você. Essa classe auxiliar pode ser usada como modelo para outros provedores criptográficos.

No interior AWS KMS, as conversões de texto cifrado bruto e os cálculos de resumo de mensagens do SM2 DSA ocorrem automaticamente. Nem todos os provedores de criptografia SM2 implementam da mesma forma. Algumas bibliotecas, como as versões 1.1.1 e posteriores do [OpenSSL](https://openssl.org/), executam essas ações automaticamente. AWS KMS confirmou esse comportamento em testes com o OpenSSL versão 3.0. Use a seguinte classe `SM2OfflineOperationHelper` com bibliotecas, como a [Bouncy Castle](https://www.bouncycastle.org/java.html), que exigem que você faça essas conversões e cálculos manualmente.

A classe `SM2OfflineOperationHelper` fornece métodos para as seguintes operações:
+   
**Cálculo de resumo de mensagem**  
Para gerar um resumo de mensagens offline que você possa usar para verificação off-line ou que você possa passar AWS KMS para assinar, use o `calculateSM2Digest` método. O `calculateSM2Digest` método gera um resumo da mensagem com o algoritmo de SM3 hash. A [GetPublicKey](https://docs.aws.amazon.com/kms/latest/APIReference/API_GetPublicKey.html)API retorna sua chave pública em formato binário. Você deve analisar a chave binária em um Java PublicKey. Forneça a chave pública analisada com a mensagem. O método combina automaticamente sua mensagem com o ID distintivo padrão, `1234567812345678`, mas você pode definir seu próprio ID distintivo alterando o valor de `DEFAULT_DISTINGUISHING_ID`.
+   
**Verificar**  
Para verificar uma assinatura offline, use o método `offlineSM2DSAVerify`. O método `offlineSM2DSAVerify` usa o resumo de mensagem calculado com base no ID distintivo especificado e a mensagem original fornecida para verificar a assinatura digital. A [GetPublicKey](https://docs.aws.amazon.com/kms/latest/APIReference/API_GetPublicKey.html)API retorna sua chave pública em formato binário. Você deve analisar a chave binária em um Java PublicKey. Forneça a chave pública analisada com a mensagem original e a assinatura que você deseja verificar. Para obter mais detalhes, consulte [Verificação off-line com pares de SM2 chaves](#key-spec-sm-offline-verification).
+   
**Encrypt**  
Para criptografar texto sem formatação offline, use o método `offlineSM2PKEEncrypt`. Esse método garante que o texto cifrado esteja em um formato que AWS KMS possa ser descriptografado. O `offlineSM2PKEEncrypt` método criptografa o texto sem formatação e, em seguida, converte o texto cifrado bruto produzido pelo SM2 PKE no formato ASN.1. A [GetPublicKey](https://docs.aws.amazon.com/kms/latest/APIReference/API_GetPublicKey.html)API retorna sua chave pública em formato binário. Você deve analisar a chave binária em um Java PublicKey. Forneça a chave pública analisada com o texto simples que você deseja criptografar.  
Se não tiver certeza de que a conversão é necessário, use a seguinte operação OpenSSL para testar o formato do texto cifrado. Se a operação falhar, significa que você precisa converter o texto cifrado no formato ASN.1.  

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

Por padrão, a `SM2OfflineOperationHelper` classe usa o ID distintivo padrão,`1234567812345678`, ao gerar resumos de mensagens para operações de SM2 DSA.

```
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");
    }
}
```