导入非对称密钥材料

当创建密钥材料来源为外部的非对称密钥时,KMS不会生成密钥材料,您需要导入自己的密钥材料。本文介绍如何为非对称密钥导入密钥材料。

重要

如果您的软件密钥管理实例、硬件密钥管理实例不支持导入密钥材料,或者导入密钥材料时返回失败,请联系阿里云技术支持升级实例。

功能介绍

密钥是KMS的基本资源,由密钥ID、基本元数据(如密钥状态等)以及密钥材料组成。创建密钥时,您可以选择由KMS生成密钥材料,也可以选择外部来源的密钥材料。如果选择了外部来源的密钥材料,您需要将外部密钥材料导入到密钥中,该功能通常被称为自带密钥(BYOK)。

不同KMS密钥管理类型对导入密钥材料的支持情况,请参见下表。关于密钥管理类型的更多信息,请参见密钥管理类型和密钥规格

  • 对:表示支持导入相应的密钥材料。

  • 错:表示不支持导入相应的密钥材料。

密钥管理类型

导入对称密钥材料

导入非对称密钥材料

默认密钥

  • 主密钥:√

  • 服务密钥:×

  • 主密钥:×

  • 服务密钥:×

软件密钥

硬件密钥

注意事项

  • 为密钥首次导入密钥材料后,密钥即和该密钥材料绑定,不再支持导入其他密钥材料。

  • 您可以根据需要将相同的密钥材料多次导入到KMS密钥中,但不能将不同的密钥材料导入到一个KMS密钥。

  • 如果密钥的密钥材料过期或被删除,您可以为密钥再次导入相同的密钥材料,使得该密钥再次可用,导入密钥材料后不支持导出,因此请您妥善保管密钥材料。

前提条件

已购买和启用KMS实例。具体操作,请参见购买和启用KMS实例

通过控制台导入密钥材料

步骤一:创建非对称密钥

导入密钥材料前,请创建密钥材料来源为外部的非对称密钥。

软件密钥

  1. 登录密钥管理服务控制台,在顶部菜单栏选择地域后,在左侧导航栏单击资源 > 密钥管理

  2. 用户主密钥页签,实例ID选择软件密钥管理实例,单击创建密钥

  3. 创建密钥面板,完成配置项设置,然后单击确定

    配置项

    说明

    密钥类型

    选择非对称密钥。

    密钥规格

    非对称密钥规格:RSA_2048、RSA_3072、EC_P256、EC_P256K

    密钥用途

    密钥的用途。取值:

    • Encrypt/Decrypt:数据加密和解密。

    • Sign/Verify:产生和验证数字签名。

    密钥别名

    密钥的别名标识符。支持英文字母、数字、下划线(_)、短划线(-)和正斜线(/)。

    标签

    密钥的标签,方便您对密钥进行分类管理。每个标签由一个键值对(Key:Value)组成,包含标签键(Key)、标签值(Value)。

    说明
    • 标签键和标签值的格式:最多支持128个字符,可以包含英文大小写字母、数字、正斜线(/)、反斜线(\)、下划线(_)、短划线(-)、半角句号(.)、加号(+)、等于号(=)、半角冒号(:)、字符at(@)、空格。

    • 标签键不能以aliyun或acs:开头。

    • 每个密钥最多可以设置20个标签键值对。

    描述信息

    密钥的说明信息。

    高级选项

    • 策略配置:详细信息,请参见密钥策略概述

    • 密钥材料来源:选择外部(导入密钥材料)

      说明

      请仔细阅读并选中我了解使用外部密钥材料的方法和意义

硬件密钥

  1. 登录密钥管理服务控制台,在顶部菜单栏选择地域后,在左侧导航栏单击资源 > 密钥管理

  2. 用户主密钥页签,实例ID选择硬件密钥管理实例,单击创建密钥

  3. 创建密钥面板,完成配置项设置,然后单击确定

    配置项

    说明

    密钥类型

    选择非对称密钥。

    密钥规格

    非对称密钥规格:RSA_2048、RSA_3072、RSA_4096、EC_P256、EC_P256K、EC_SM2

    密钥用途

    密钥的用途。取值:

    • Encrypt/Decrypt:数据加密和解密。

    • Sign/Verify:产生和验证数字签名。

    密钥别名

    密钥的别名标识符。支持英文字母、数字、下划线(_)、短划线(-)和正斜线(/)。

    标签

    密钥的标签,方便您对密钥进行分类管理。每个标签由一个键值对(Key:Value)组成,包含标签键(Key)、标签值(Value)。

    说明
    • 标签键和标签值的格式:最多支持128个字符,可以包含英文大小写字母、数字、正斜线(/)、反斜线(\)、下划线(_)、短划线(-)、半角句号(.)、加号(+)、等于号(=)、半角冒号(:)、字符at(@)、空格。

    • 标签键不能以aliyun或acs:开头。

    • 每个密钥最多可以设置20个标签键值对。

    描述信息

    密钥的说明信息。

    高级选项

    • 策略配置:详细信息,请参见密钥策略概述

    • 密钥材料来源:选择外部(导入密钥材料)

      说明

      请仔细阅读并选中我了解使用外部密钥材料的方法和意义

步骤二:下载包装公钥和导入令牌

导入密钥材料的参数包含包装公钥和导入令牌,包装公钥用于加密密钥材料,在导入过程中保护您的密钥材料,导入令牌用于导入密钥材料。

  1. 定位到目标密钥,单击操作列的详情,在密钥详情页面的密钥材料区域,单击获取导入参数

  2. 获取导入密钥材料的参数对话框,选择公钥类型加密算法后,单击下一步

    密钥管理类型

    KMS密钥的规格

    包装公钥类型

    加密算法

    软件密钥

    • RSA_2048

    • RSA_3072

    • EC_P256

    • EC_P256K

    RSA_2048

    RSAES_OAEP_SHA_256_AES_256_ECB_PKCS7_PAD

    硬件密钥

    • RSA_2048

    • RSA_3072

    • RSA_4096

    • EC_P256

    • EC_P256K

    RSA_2048

    RSAES_OAEP_SHA_256_AES_256_ECB_PKCS7_PAD

    EC_SM2

    EC_SM2

    SM2PKE

  3. 下载包装公钥以及导入令牌,并妥善保存。

    • 公钥格式

      • der格式:下载后文件名默认为publickey_******.bin。

      • pem格式:下载后文件名默认为publickey_******.pem。

    • 导入令牌:下载后文件名默认为token_******.txt。

      重要
      • 导入令牌的有效期为24小时,在有效期内可以重复使用,失效后需要获取新的导入令牌和公钥。

      • 包装公钥和导入令牌必须配套使用。即不允许下载两次包装公钥和导入令牌,使用其中一个的包装公钥,另一个的导入令牌。

步骤三:使用包装公钥加密密钥材料

请在您的系统环境中生成并加密密钥材料,过程中会使用到以下密钥,具体说明请参见下表。

密钥

用途

提供者

标记说明

目标非对称密钥TAK(Target Asymmetric Key)

待导入的目标非对称密钥。

您的系统环境或者工具(如线下的密钥管理设施KMI,或者线下的硬件安全模块HSM)。

  • TAKpub:公钥部分

  • TAKpriv:私钥部分

加密密钥IWK(Import Wrapping Key)

用于导入TAK的加密密钥。

阿里云KMS。

  • IWKpub:公钥部分

    说明

    即您在密钥管理服务控制台下载的包装公钥。

  • IWKpriv:私钥部分

瞬时密钥ESK(Ephemeral Symmetric Key)

一个瞬时存在的对称密钥,用于直接加密TAKpriv。

源环境的系统或者工具,在完成对TAK的导出操作后请立即销毁。

不涉及

  1. 创建一个目标非对称密钥私钥(TAKpriv),密钥规格与您创建非对称密钥时选择的密钥规格一致。如果您已有目标非对称密钥私钥(TAKpriv),请跳过本步骤。

    说明

    TAKpriv格式需要遵循:RSA私钥根据RFC3447进行编码,ECC私钥根据RFC5915进行编码,然后根据RFC5208包装为PKCS#8格式。

  2. 创建一个瞬时密钥(ESK)。

  3. 使用加密密钥公钥(IWKpub)来加密瞬时密钥(ESK),得到瞬时密钥密文(Cipher(ESK))。

  4. 使用瞬时密钥(ESK)加密目标非对称密钥私钥(TAKpriv),得到目标非对称密钥的私钥密文(Cipher(TAKpriv))。

  5. 按照Cipher(ESK)||Cipher(TAKpriv)格式组装结果数据,得到加密后的密钥材料。

示例:使用GMSSL生成SM2算法的密钥材料(仅硬件密钥管理实例支持)

  1. 创建一个SM2算法的目标非对称密钥私钥(TAKpriv),并获取私钥的椭圆曲线的参数D。

    #生成SM2私钥。
    gmssl ecparam -name sm2p256v1 -genkey -outform DER -out TakSm2Priv.bin
    #查看私钥的D值。
    gmssl asn1parse -inform DER -in TakSm2Priv.bin
    
    #将D值写入文件。
    echo 'CB55E5ECC20BF7E9249ADEDE990EF141D14252E024734EB058A6B9F103F12A04' | xxd -r -p > TakSm2D.bin
  2. 创建一个SM4算法的瞬时密钥(ESK)。

    gmssl rand -out EskSm4.bin 16
  3. 使用加密密钥公钥(IWKpub),采用SM2算法加密瞬时密钥(ESK),得到瞬时密钥密文(Cipher(ESK))。

    gmssl sm2utl -encrypt -in EskSm4.bin -pubin -inkey PublicKey.pem -out CipherEsk.bin
    说明

    请将PublicKey.pem替换为您在密钥管理服务控制台下载的公钥文件的名称。

  4. 使用瞬时密钥(ESK)加密目标非对称密钥私钥(TAKpriv)的椭圆曲线参数D ,生成目标非对称密钥的私钥密文(Cipher(TAKpriv))。其中加密模式为ECB,填充模式为NoPadding。

    xxd -l 16  -c 16 -ps EskSm4.bin | xargs -I {} openssl enc -sms4-ecb -e  -K {} -in 
    TakSm2D.bin -nosalt -nopad -out CipherTakPriv.bin
  5. 按照Cipher(ESK) || Cipher(TAKpriv)格式组装结果数据,得到加密后的密钥材料。

    cat CipherEsk.bin CipherTakPriv.bin > EncryptedKeyMaterial.bin

示例:使用OPENSSL生成RSA_2048算法的密钥材料

  1. 创建一个RSA_2048算法的目标非对称密钥私钥,并且将私钥转为PKCS#8格式。

    openssl genrsa -out TakPrivPkcs1.pem 2048
    openssl pkcs8 -topk8 -inform PEM -in TakPrivPkcs1.pem -outform der -nocrypt -out TakPrivPkcs8.bin
  2. 创建一个AES_256算法的瞬时密钥(ESK)。

    openssl rand -out EskAes256.bin 32
  3. 使用加密密钥公钥(IWKpub)加密瞬时密钥(ESK),得到瞬时密钥密文(Cipher(ESK))。加密时采用RSAES OAEP标准加密,其中MGF1和哈希算法为SHA256。

    openssl pkeyutl -encrypt -pubin -inkey PublicKey.pem  -in EskAes256.bin  -pkeyopt \
    rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 -pkeyopt rsa_mgf1_md:sha256 -out \
    CipherEsk.bin
    说明

    请将PublicKey.pem替换为您在密钥管理服务控制台下载的公钥文件的名称。

  4. 使用瞬时密钥(ESK)加密目标非对称密钥私钥(TAKpriv),生成目标非对称密钥的私钥密文(Cipher(TAKpriv))。加密模式为ECB,填充模式为PKCS#7 Padding。

    xxd -l 32  -c 32 -ps EskAes256.bin | xargs -I {} openssl enc  -aes-256-ecb -e  -K {} -in 
    TakPrivPkcs8.bin -nosalt -out CipherTakPriv.bin
  5. 按照Cipher(ESK) || Cipher(TAKpriv)格式组装结果数据,再进行Base64编码。

    cat CipherEsk.bin CipherTakPriv.bin > EncryptedKeyMaterial.bin
    openssl enc -e -base64 -A -in EncryptedKeyMaterial.bin -out EncryptedKeyMaterial_base64.txt
    说明

    EncryptedKeyMaterial_base64.txt即为可导入KMS的密钥材料文件。

步骤四:导入密钥材料

在密钥详情页面,单击导入密钥材料,在导入打包后的密钥材料对话框,完成各项配置后,单击确定

配置项

说明

打包后的密钥材料

上传步骤三:使用包装公钥加密密钥材料中生成的密钥材料文件。

导入令牌

上传步骤二:下载包装公钥和导入令牌中下载的令牌文件。

密钥材料过期时间

支持选择永不过期,也可以自定义过期时间。

重要

如果设置了密钥材料过期时间,在设置的时间点之后KMS会删除已过期的密钥材料,您将无法使用该密钥材料。如需恢复使用,可以为密钥再次导入相同的密钥材料。

导入密钥材料成功后,密钥状态从待导入更新为启用中

通过SDK导入密钥材料

您可以通过阿里云SDK在KMS中创建RSA、ECC和SM2算法的密钥,并导入密钥材料。Java代码示例如下。

说明

阿里云账号AccessKey拥有所有OpenAPI的访问权限,建议您使用RAM用户进行API访问或日常运维。强烈建议不要把AccessKey ID和AccessKey Secret保存到工程代码里,否则可能导致AccessKey泄露,威胁您账号下所有资源的安全。

本示例以将AccessKey配置在环境变量ALIBABA_CLOUD_ACCESS_KEY_ID和ALIBABA_CLOUD_ACCESS_KEY_SECRET的方式来实现身份验证为例。

import java.security.InvalidAlgorithmParameterException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.interfaces.ECPrivateKey;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Random;

import javax.crypto.Cipher;
import javax.crypto.spec.OAEPParameterSpec;
import javax.crypto.spec.PSource.PSpecified;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

import com.aliyuncs.AcsRequest;
import com.aliyuncs.AcsResponse;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.exceptions.ServerException;
import com.aliyuncs.http.FormatType;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.http.ProtocolType;
import com.aliyuncs.kms.model.v20160120.CreateKeyRequest;
import com.aliyuncs.kms.model.v20160120.CreateKeyResponse;
import com.aliyuncs.kms.model.v20160120.GetParametersForImportRequest;
import com.aliyuncs.kms.model.v20160120.GetParametersForImportResponse;
import com.aliyuncs.kms.model.v20160120.ImportKeyMaterialRequest;
import com.aliyuncs.kms.model.v20160120.ImportKeyMaterialResponse;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import org.apache.commons.lang3.tuple.Pair;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

public class BringYourOwnAsymmetricKeySample {
    static String regionId = "cn-hangzhou";
    static String accessKeyId = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID");
    static String accessKeySecret = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
    static String dedicatedKmsInstanceId = "*** Provide your DedicatedKmsInstanceId ***";
    DefaultAcsClient kmsClient;
    private final String SM2PKE_SM4_ECB = "SM2PKE_SM4_ECB";
    private final String RSAES_OAEP_SHA_256_AES_256_ECB_PKCS7_PAD = "RSAES_OAEP_SHA_256_AES_256_ECB_PKCS7_PAD";
    private static Provider BC = new BouncyCastleProvider();
    private static X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
    private static ECDomainParameters ecDomainParameters = new ECDomainParameters(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN());
    static {
        java.security.Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
    }

    public static void main(String[] args) {
        //初始化KMS SDK。
        DefaultAcsClient client = getClientForPublicEndpoint(regionId, accessKeyId, accessKeySecret);
        BringYourOwnAsymmetricKeySample sample = new BringYourOwnAsymmetricKeySample(client);

        //创建并导入EC_SM2类型的外部密钥。
        sample.doByok("EC_SM2", "EC_SM2", sample.SM2PKE_SM4_ECB, "SM4");
        //创建并导入EC_P256类型的外部密钥。
        sample.doByok("EC_P256", "RSA_2048", sample.RSAES_OAEP_SHA_256_AES_256_ECB_PKCS7_PAD, "AES_256");
        //创建并导入RSA类型的外部密钥。
        sample.doByok("RSA_2048", "RSA_2048", sample.RSAES_OAEP_SHA_256_AES_256_ECB_PKCS7_PAD, "AES_256");
    }


    public static DefaultAcsClient getClientForPublicEndpoint(String regionId, String accessKeyId, String accessKeySecret) {
        /**
         * Construct an Aliyun Client:
         * Set RegionId, AccessKeyId and AccessKeySecret
         */
        IClientProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret);
        DefaultAcsClient client = new DefaultAcsClient(profile);
        return client;
    }

    public BringYourOwnAsymmetricKeySample(DefaultAcsClient kmsClient) {
        this.kmsClient = kmsClient;
    }

    public void doByok(String targetKeySpec, String wrappingKeySpec, String wrappingAlgorithm, String ephemeralKeySpec) {
        try {
            //创建ECC外部密钥。
            CreateKeyResponse.KeyMetadata keyMetadata = this.createExternalKeyInDkms(dedicatedKmsInstanceId, targetKeySpec, "SIGN/VERIFY");
            String keyId = keyMetadata.getKeyId();
            //获取导入密钥材料。
            GetParametersForImportResponse parametersForImportResponse = this.getParametersForImport(keyId, wrappingKeySpec,
                wrappingAlgorithm);
            String importToken = parametersForImportResponse.getImportToken();
            String publicKeyBase64 = parametersForImportResponse.getPublicKey();
            //生成瞬时对称密钥。
            byte[] ephemeralSymmetricKeyPlaintext = this.generateEphemeralSymmetricKey(ephemeralKeySpec);
            //生成目标非对称密钥。
            byte[] targetAsymmetricKeyPlaintext = this.generateTargetAsymmetricKey(targetKeySpec);
            //使用加密公钥加密瞬时对称密钥。
            byte[] ephemeralSymmetricKeyCipher = this.encryptEphemeralSymmetricKey(publicKeyBase64,
                wrappingAlgorithm, ephemeralSymmetricKeyPlaintext);
            //使用瞬时对称密钥加密目标非对称密钥。
            byte[] targetAsymmetricKeyCipher = this.encryptTargetAsymmetricKey(ephemeralSymmetricKeyPlaintext, targetAsymmetricKeyPlaintext,
                wrappingAlgorithm);
            //生成密钥材料。
            byte[] encryptedKeyMaterial = new byte[ephemeralSymmetricKeyCipher.length + targetAsymmetricKeyCipher.length];
            System.arraycopy(ephemeralSymmetricKeyCipher, 0, encryptedKeyMaterial, 0, ephemeralSymmetricKeyCipher.length);
            System.arraycopy(targetAsymmetricKeyCipher, 0, encryptedKeyMaterial, ephemeralSymmetricKeyCipher.length, targetAsymmetricKeyCipher.length);
            String encryptedKeyMaterialBase64 =  DatatypeConverter.printBase64Binary(encryptedKeyMaterial);
            //导入密钥材料到KMS。
            this.importKeyMaterial(keyId, encryptedKeyMaterialBase64, importToken, 0L);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private GetParametersForImportResponse getParametersForImport(String keyId, String keySpec, String algorithm) throws Exception {
        GetParametersForImportRequest request = new GetParametersForImportRequest();
        request.setAcceptFormat(FormatType.JSON);
        request.setMethod(MethodType.POST);
        request.setProtocol(ProtocolType.HTTPS);
        request.setKeyId(keyId);
        request.setWrappingKeySpec(keySpec);
        request.setWrappingAlgorithm(algorithm);
        GetParametersForImportResponse resp;
        try {
            resp = this.getAcsResponseWithRetry(request);
        } catch (Exception e) {
            throw e;
        }
        return resp;
    }

    private CreateKeyResponse.KeyMetadata createExternalKeyInDkms(String dedicatedKmsInstance, String keySpec, String keyUsage) throws Exception {
        CreateKeyRequest request = new CreateKeyRequest();
        //创建外部密钥。  
        request.setOrigin("EXTERNAL"); 
        request.setKeyStoreId(dedicatedKmsInstance);
        request.setKeySpec(keySpec);
        request.setKeyUsage(keyUsage);

        request.setProtocol(ProtocolType.HTTPS);
        request.setAcceptFormat(FormatType.JSON);
        request.setMethod(MethodType.POST);
        CreateKeyResponse.KeyMetadata ret = null;
        String requestId = null;
        try {
            CreateKeyResponse response = getAcsResponseWithRetry(request);
            ret = response.getKeyMetadata();
            requestId = response.getRequestId();
        } catch (Exception e) {
            throw e;
        }
        return Pair.of(ret, requestId).getKey();
    }

    private <T extends AcsResponse> T getAcsResponseWithRetry(AcsRequest<T> request) throws ServerException,
        ClientException {
        String expStr = "Retry Max Times";
        for (int i = 0; i < 3; i++) {
            try {
                T resp = this.kmsClient.getAcsResponse(request);
                if (resp == null) {
                    throw new ClientException("Get a null response");
                }
                return resp;
            } catch (ServerException e) {
                throw e;
            } catch (ClientException e) {
                expStr = e.toString();
              //need retry
                if (expStr.contains("SDK.ServerUnreachable")) {
                    continue;
                }
                throw e;
            }
        }
        throw new ClientException(expStr);
    }

    private byte[] generateEphemeralSymmetricKey(String ephemeralSymmetricKeySpec) throws Exception {
       //瞬时对称密钥是AES_256时,长度为32比特。  
       int ephemeralSymmetricKeyLength = 32; 
        if ("SM4".equals(ephemeralSymmetricKeySpec)) {
            ephemeralSymmetricKeyLength = 16;
        }
        byte[] key = new byte[32];
        new Random().nextBytes(key);

        return key;
    }

    private byte[] generateTargetAsymmetricKey(String keySpec) throws Exception {
        PrivateKey privateKey = null;
        //生成SM2密钥,并获取私钥的D值。
        if ("EC_SM2".equals(keySpec)) {
            ECPrivateKey ecPrivateKey = (ECPrivateKey)generateSm2KeyPair().getPrivate();
            byte[] dT = ecPrivateKey.getS().toByteArray();
            byte[] d = new  byte[32];
            if (dT.length == 33) {
                System.arraycopy(dT, 1, d, 0, 32);
            }
            return dT.length == 32 ? dT : d;
        }

        //生成RSA或者ECC私钥。
        if (keySpec.contains("RSA")) {
            String[] keySpecAttrs = keySpec.split("_");
            int bits = Integer.parseInt(keySpecAttrs[keySpecAttrs.length - 1]);
            privateKey = generateRsaKeyPair(bits).getPrivate();
        } else if  (keySpec.contains("EC")) {
            if (keySpec.contains("P256K")) {
                //生成EC_P256K私钥。
                privateKey  = generateEccKeyPair("secp256k1").getPrivate();
            } else {
                //生成EC_P256私钥。
                privateKey=   generateEccKeyPair("secp256r1").getPrivate();
            }
        }
        if (privateKey != null) {
            //返回PKCS#8格式的私钥。
            return  privateKey.getEncoded();
        }
        return null;
    }

    private  KeyPair generateEccKeyPair(String keySpec)
        throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
        ECGenParameterSpec ecSpec = new ECGenParameterSpec(keySpec);
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
        keyPairGenerator.initialize(ecSpec, new SecureRandom());
        return keyPairGenerator.generateKeyPair();
    }
    private  KeyPair generateRsaKeyPair(int length) throws Exception {
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
        keyGen.initialize(length);
        return keyGen.genKeyPair();
    }

    private KeyPair generateSm2KeyPair() throws Exception {
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC", "BC");
        keyGen.initialize(new ECGenParameterSpec("sm2p256v1"), new SecureRandom());
        return keyGen.genKeyPair();
    }


    private byte[] encryptEphemeralSymmetricKey (String publicKeyBase64, String wrappingAlgorithm, byte[] ephemeralSymmetricKeyPlaintext) throws Exception {
        PublicKey publickey = null;
        byte[] enchbk = null;
        if ("RSAES_OAEP_SHA_256_AES_256_ECB_PKCS7_PAD".equals(wrappingAlgorithm)) {
            publickey = parseDerPublicKey("RSA", publicKeyBase64);
            Cipher oaepFromAlgo = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
            OAEPParameterSpec oaepParams = new OAEPParameterSpec("SHA-256", "MGF1", new MGF1ParameterSpec("SHA-256"), PSpecified.DEFAULT);
            oaepFromAlgo.init(Cipher.ENCRYPT_MODE, publickey, oaepParams);
            enchbk = oaepFromAlgo.doFinal(ephemeralSymmetricKeyPlaintext);
        } else if ("SM2PKE_SM4_ECB".equals(wrappingAlgorithm)) {
            publickey = parseDerPublicKey("EC", publicKeyBase64, BC);
            BCECPublicKey localECPublicKey = (BCECPublicKey) publickey;
            ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(localECPublicKey.getQ(), ecDomainParameters);
            SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
            sm2Engine.init(true, new ParametersWithRandom(ecPublicKeyParameters));
            enchbk = sm2Engine.processBlock(ephemeralSymmetricKeyPlaintext, 0, ephemeralSymmetricKeyPlaintext.length);

        } else {
            throw new Exception("Invalid wrappingAlgorithm");
        }
        return enchbk;
    }

    private PublicKey parseDerPublicKey(String keyType, String pemKey) throws Exception {
        byte[] derKey = DatatypeConverter.parseBase64Binary(pemKey);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(derKey);
        return KeyFactory.getInstance(keyType).generatePublic(keySpec);
    }
    private PublicKey parseDerPublicKey(String keyType, String pemKey, Provider provider) throws Exception {
        byte[] derKey = DatatypeConverter.parseBase64Binary(pemKey);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(derKey);
        return KeyFactory.getInstance(keyType, provider).generatePublic(keySpec);
    }

    private byte[] encryptTargetAsymmetricKey (byte[] secretKey, byte[] targetAsymmetricKeyPlaintext, String wrappingAlgorithm)
        throws Exception {
        if ("RSAES_OAEP_SHA_256_AES_256_ECB_PKCS7_PAD".equals(wrappingAlgorithm)) {
            SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey, "AES");
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding", "BC");
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
            return cipher.doFinal(targetAsymmetricKeyPlaintext);
        } else if ("SM2PKE_SM4_ECB".equals(wrappingAlgorithm)) {
            SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey, "SM4");
            Cipher cipher = Cipher.getInstance("SM4/ECB/NoPadding");
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
            return cipher.doFinal(targetAsymmetricKeyPlaintext);
        }

        throw new Exception("Invalid WrappingAlgorithm");
    }

    private boolean importKeyMaterial(
        String keyId,
        String material,
        String token,
        Long expire
    ) throws Exception {
        ImportKeyMaterialRequest req = newImportKeyMaterialRequest(
            keyId, material, token, expire);
        try {
            ImportKeyMaterialResponse resp = this.getAcsResponseWithRetry(req);
        } catch (Exception e) {
            throw e;
        }
        return true;
    }

    private ImportKeyMaterialRequest newImportKeyMaterialRequest(
        String keyId,
        String material,
        String token,
        Long expire
    ) {
        ImportKeyMaterialRequest request = new ImportKeyMaterialRequest();
        request.setAcceptFormat(FormatType.JSON);
        request.setMethod(MethodType.POST);
        request.setProtocol(ProtocolType.HTTPS);
        request.setEncryptedKeyMaterial(material);
        request.setImportToken(token);
        request.setKeyId(keyId);
        request.setKeyMaterialExpireUnix(expire);
        return request;
    }
}
            

常见问题

是否支持删除密钥材料?

支持删除。

重要

导入的密钥材料过期或者被删除后,其密钥将无法使用,需要再次导入相同的密钥材料才可正常使用。

  • 直接删除密钥材料

    • 控制台:在密钥详情页的密钥材料区域,单击删除密钥材料

    • API接口:调用DeleteKeyMaterial接口删除,该操作不会删除您的密钥。

  • 过期后由KMS删除

    在导入密钥材料时设置过期时间,在时间点之后KMS会删除已过期的密钥材料。

如何重新导入相同的密钥材料?

密钥材料过期或删除后,您可以再次导入相同的密钥材料,密钥才可继续使用。

  1. 删除过期的密钥材料。

    在密钥详情页,单击密钥材料页签,单击删除密钥材料

  2. 重新下载包装公钥和导入令牌。具体操作,请参见步骤二:下载包装公钥和导入令牌

    说明

    密钥包装过程不会影响密钥材料的内容,因此,您可以使用不同的包装公钥和不同的包装算法来导入相同的密钥材料。

  3. 使用包装公钥加密密钥材料。具体操作,请参见步骤三:使用包装公钥加密密钥材料

    说明

    密钥材料必须与之前过期的密钥材料为同一个。

  4. 使用导入令牌,导入加密后的密钥材料。具体操作,请参见步骤四:导入密钥材料

如何判断密钥材料是由外部导入还是由KMS生成?

  • 方式一:在密钥管理服务控制台查看。

    密钥管理页面,单击用户主密钥页签,选择实例ID后定位到目标密钥,单击操作列的详情,在详情页面查看密钥材料来源

  • 方式二:通过调用DescribeKey接口查看。

    如果Origin值为EXTERNAL,说明密钥材料由外部导入。如果Origin值为Aliyun_KMS,说明密钥材料由KMS生成。

如何轮转使用外部密钥材料的密钥?

对导入外部密钥材料的密钥,KMS不提供定期自动轮转功能。如果您需要轮转,只能创建一个新的密钥然后导入新的密钥材料。

相关文档