本文为您介绍如何导入外部密钥材料以及删除已导入的外部密钥材料。

前提条件

进行操作前,请确保您已经注册了阿里云账号。如还未注册,请先完成账号注册

背景信息

用户主密钥(CMK)是KMS的基本资源,是由密钥ID、基本元数据(如密钥状态等)以及用于加密、解密数据的密钥材料组成。默认情况下,当您CreateKey时,会由KMS生成密钥材料。 您可以选择创建密钥材料来源为外部的密钥,此时,KMS将不会为您创建的用户主密钥生成密钥材料,您可以将自己的密钥材料导入到CMK中。 您可以通过DescribeKey来判断密钥材料来源情况。当KeyMetadata中OriginAliyun_KMS时,说明密钥材料由KMS生成,称为普通密钥;当OriginEXTERNAL时,说明密钥材料由外部导入,称为外部密钥

在您选择密钥材料来源为外部,使用您自己导入的密钥材料的时候,您需要注意以下几点:
  • 您需要保证使用了符合要求的随机源生成密钥材料。
  • 您需要保证密钥材料的可靠性。
    • KMS确保导入密钥材料高可用,但是不能保证导入密钥材料与KMS生成的密钥材料具有相同的可靠性。
    • 您导入的密钥材料可以直接通过DeleteKeyMaterial进行删除,也可以设置过期时间,在密钥材料过期后也会被删除(CMK不会被删除)。KMS生成的密钥材料无法直接被删除,只能通过ScheduleKeyDeletion,在等待7到30天后,随着CMK一起被删除。
    • 当导入的密钥材料被删除后,可以再次导入相同的密钥材料使得CMK再次可用,因此您需要自行保存密钥材料的副本。
  • 每个CMK只能拥有一个导入密钥材料。一旦您将一个密钥材料导入CMK,该CMK将与该密钥材料绑定,即便密钥材料已经过期或者被删除,也不能导入其他密钥材料。如果您需要轮换使用外部密钥材料的CMK,只能创建一个新的CMK然后导入新的密钥材料。
  • CMK具有独立性。您使用一个CMK加密的数据,无法使用其他CMK进行解密,即便这些CMK都使用相同的密钥材料。
  • 只能导入256位对称密钥作为密钥材料。

导入密钥材料

  1. 创建外部密钥。
    • 在控制台创建密钥的高级选项中,选择密钥材料来源为外部。
    • 调用CreateKey,指定参数OriginEXTERNAL
      aliyuncli kms CreateKey --Origin EXTERNAL --Description "External key"
  2. 获取导入密钥材料参数。
    导入密钥材料参数包括一个用于加密密钥材料的公钥,以及一个导入令牌。
    • 通过控制台导入密钥材料的参数。
    • 调用GetParametersForImport
      aliyuncli kms GetParametersForImport --KeyId 1339cb7d-54d3-47e0-b595-c7d3dba8**** --WrappingAlgorithm RSAES_OAEP_SHA_1 --WrappingKeySpec RSA_2048
  3. 导入密钥材料。
    导入密钥材料可以为从未导入过密钥材料的外部密钥导入密钥材料,也可以重新导入已经过期和已被删除的密钥材料,或者重置密钥材料的过期时间。 导入令牌与加密密钥材料的公钥具有绑定关系,同时一个令牌只能为其生成时指定的主密钥导入密钥材料;导入令牌的有效期为24小时,在有效期内可以重复使用,失效以后需要获取新的导入令牌和加密公钥。
    1. 使用加密公钥对密钥材料进行加密。
      加密公钥是一个2048比特的RSA公钥,使用的加密算法需要与获取导入密钥材料参数时指定的一致。由于API返回的加密公钥是经过Base64编码的,因此在使用时需要先进行Base64解码。目前KMS支持的加密算法有RSAES_OAEP_SHA_1、RSAES_OAEP_SHA_256与RSAES_PKCS1_V1_5。
    2. 将加密后的密钥材料进行Base64编码。
    3. 将编码后的密钥材料与导入令牌一起,作为GenerateDataKey的参数导入KMS。
      aliyuncli kms ImportKeyMaterial --KeyId 1339cb7d-54d3-47e0-b595-c7d3dba8**** --EncryptedKeyMaterial xxx --ImportToken xxxx
  4. 删除密钥材料。

    当您导入了密钥材料以后,您就可以像使用普通密钥一样使用外部密钥了。与普通密钥不同的是,外部密钥的密钥材料可能会过期,也可以由您手工删除。当密钥材料过期或者被删除以后,密钥将无法继续使用,由该密钥加密的密文也无法被解密,除非您重新导入相同的密钥材料。

    如果密钥在密钥材料过期或者密钥材料删除时处于待删除(PendingDeletion)状态时,密钥状态不会改变,否则密钥状态会变为等待导入(PendingImport)。

    • 通过控制台删除密钥。
    • 调用DeleteKeyMaterial
      aliyuncli kms DeleteKeyMaterial --KeyId 1339cb7d-54d3-47e0-b595-c7d3dba8****      

使用OPENSSL加密密钥材料并上传

  1. 创建一个外部密钥。
  2. 创建一个密钥材料。
    密钥材料必须是256位的对称密钥,使用OPENSSL产生一个32字节的随机数进行演示。
    openssl rand -out KeyMaterial.bin 32
  3. 获取导入密钥材料参数。
  4. 加密密钥材料。
    1. 将加密公钥进行Base64解码。
    2. 根据指定的加密算法(以RSAES_OAEP_SHA_1为例)加密密钥材料。
    3. 将加密后的密钥材料进行Base64编码,保存为文本文件。
      openssl rand -out KeyMaterial.bin 32
      openssl enc -d -base64 -A -in PublicKey_base64.txt -out PublicKey.bin
      openssl rsautl -encrypt -in KeyMaterial.bin -oaep -inkey PublicKey.bin  -keyform DER  -pubin -out EncryptedKeyMaterial.bin
      openssl enc -e -base64 -A -in EncryptedKeyMaterial.bin -out EncryptedKeyMaterial_base64.txt
    4. 上传加密后的密钥材料与导入令牌。

操作样例

使用JAVA SDK加密密钥材料并上传。

//使用最新KMS JAVA SDK
//KmsClient.java

import com.aliyuncs.kms.model.v20160120.*;
import com.aliyuncs.profile.DefaultProfile;

//KMS API封装
public class KmsClient {
        DefaultAcsClient client;

        public KmsClient( String region_id, String ak, String secret) {
                DefaultProfile profile = DefaultProfile.getProfile(region_id, ak, secret);
                this.client = new DefaultAcsClient(profile);
        }

        public CreateKeyResponse createKey() throws Exception {
                CreateKeyRequest request = new CreateKeyRequest();
                request.setOrigin("EXTERNAL"); //创建外部密钥
                return this.client.getAcsResponse(request);
        }
        //... 省略,其余API类似
}
//example.java
import com.aliyuncs.kms.model.v20160120.*;
import KmsClient
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.MGF1ParameterSpec;
import javax.crypto.Cipher;
import javax.crypto.spec.OAEPParameterSpec;
import javax.crypto.spec.PSource.PSpecified;
import java.security.spec.X509EncodedKeySpec;
import java.util.Random;
import javax.xml.bind.DatatypeConverter;

public class CreateAndImportExample {
        public static void main(String[] args) {
                String regionId = "cn-hangzhou";
        String accessKeyId = "*** Provide your AccessKeyId ***";
        String accessKeySecret = "*** Provide your AccessKeySecret ***";
        KmsClient kmsclient = new KmsClient(regionId,accessKeyId,accessKeySecret);
        //Create External Key
        try {
                CreateKeyResponse keyResponse = kmsclient.createKey();
                String keyId = keyResponse.KeyMetadata.getKeyId();
                //产生一个32位随机数
                byte[] keyMaterial = new byte[32];
                new Random().nextBytes(keyMaterial);
                //获取导入密钥材料参数
                GetParametersForImportResponse paramResponse = kmsclient.getParametersForImport(keyId,"RSAES_OAEP_SHA_256");
                String importToekn = paramResponse.getImportToken();
                String encryptPublicKey = paramResponse.getPublicKey();
                //base64解码加密公钥
                byte[] publicKeyDer = DatatypeConverter.parseBase64Binary(encryptPublicKey);
                //解析成RSA的公钥
                KeyFactory keyFact = KeyFactory.getInstance("RSA");
                X509EncodedKeySpec spec = new X509EncodedKeySpec(publicKeyDer);
                PublicKey publicKey = keyFact.generatePublic(spec);
                //加密密钥材料
                Cipher oaepFromAlgo = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
                String hashFunc = "SHA-256";
                OAEPParameterSpec oaepParams = new OAEPParameterSpec(hashFunc, "MGF1", new MGF1ParameterSpec(hashFunc), PSpecified.DEFAULT);
                oaepFromAlgo.init(Cipher.ENCRYPT_MODE, publicKey, oaepParams);
                byte[] cipherDer = oaepFromAlgo.doFinal(keyMaterial);
                //加密后的密钥材料,需要进行base64编码
                String encryptedKeyMaterial = DatatypeConverter.printBase64Binary(cipherDer);
                //导入密钥材料
                Long expireTimestamp = 1546272000L; //Unix时间戳,精确到秒,0表示永不过期
                        kmsClient.importKeyMaterial(keyId,encryptedKeyMaterial, expireTimestamp);
        } catch(Exception e) {
                //... 省略
        }
        }
}