当您创建密钥材料来源为外部的密钥时,KMS不会为您创建的用户主密钥(CMK)生成密钥材料,此时您可以将自己的密钥材料导入到CMK中。本文为您介绍如何导入外部密钥材料。
背景信息
用户主密钥(CMK)是KMS的基本资源,由密钥ID、基本元数据(如密钥状态等)以及用于加密、解密数据的密钥材料组成。默认情况下,当您创建CMK时,会由KMS生成密钥材料。您也可以选择创建密钥材料来源为外部的密钥,此时,KMS将不会为您创建的CMK生成密钥材料,您可以将自己的密钥材料导入到CMK中。
导入密钥材料时,您可以导入从未被导入过的密钥材料,也可以重新导入已经过期和已被删除的密钥材料,或者重置密钥材料的过期时间。
您可以调用DescribeKey接口判断密钥材料来源。
当KeyMetadata中Origin为Aliyun_KMS时,说明密钥材料由KMS生成,该密钥称为普通密钥。
当Origin为EXTERNAL时,说明密钥材料由外部导入,该密钥称为外部密钥。
当您使用由外部导入的密钥材料时,需要注意以下几点:
请确保您使用了符合要求的随机源生成密钥材料。
请确保密钥材料的可靠性。
KMS可以确保导入密钥材料高可用,但是不能确保导入密钥材料与KMS生成的密钥材料具有相同的可靠性。
导入的密钥材料被删除后,可以再次导入相同的密钥材料使得CMK再次可用,因此您需要自行保存密钥材料的副本。
导入密钥材料后,您可以直接调用DeleteKeyMaterial接口删除密钥材料,也可以设置过期时间,在密钥材料过期后进行删除(CMK不会被删除)。
每个CMK只能拥有一个导入密钥材料。当您将一个密钥材料导入CMK时,CMK将与密钥材料绑定,即便密钥材料已经过期或者被删除,也不能导入其他密钥材料。如果您需要轮换使用外部密钥材料的CMK,只能创建一个新的CMK然后导入新的密钥材料。
CMK具有独立性。例如:您使用CMK加密的数据,无法使用其他CMK进行解密,即便这些CMK都使用相同的密钥材料。
只能导入256位(AES)的对称密钥作为密钥材料。
通过控制台导入密钥材料
创建外部密钥。
登录密钥管理服务控制台,在页面左上角的地域下拉列表,选择密钥所在的地域。
在左侧导航栏,单击用户主密钥。
单击创建密钥,在创建密钥对话框完成各项的配置。
配置项
说明
KMS实例
选择默认,不支持修改。
密钥类型
选择对称密钥类型Aliyun_AES_256。关于对称密钥类型的更多信息,请参见对称密钥的类型。
密钥用途
选择密钥的用途。取值:
Encrypt/Decrypt:数据加密和解密。
Sign/Verify:产生和验证数字签名。
别名
用户主密钥的标识符。支持英文字母、数字、下划线(_)、短划线(-)和正斜线(/)。
更多信息,请参见别名概述。
保护级别
密钥的保护级别。取值:
Software:通过软件模块对密钥进行保护。
Hsm:将密钥托管在密码机中,使密钥获得高安全等级的专用硬件的保护。
描述
密钥的说明信息。
轮转周期
设置对称密钥自动轮转的时间周期。
说明仅密钥类型为Aliyun_AES_256的对称密钥支持设置轮转周期。
密钥材料来源
选择外部。
仔细阅读参考文档后勾选我了解使用外部密钥材料的方法和意义,然后单击确定。
获取导入密钥材料参数。
导入密钥材料参数包括一个用于加密密钥材料的公钥,以及一个导入令牌。
在左侧导航栏,单击用户主密钥。
单击目标密钥ID进入密钥管理页面,在密钥材料区域,单击获取导入参数。
在获取导入密钥材料的参数对话框,选择公钥类型和加密算法,并单击下一步。
本文以公钥类型为RSA_2048、加密算法为RSAES_OAEP_SHA_1为例进行介绍。
说明当公钥类型取值为RSA_2048时,您可以选择加密算法RSAES_PKCS1_V1_5(默认值)、RSAES_OAEP_SHA_1或RSAES_OAEP_SHA_256。
下载公钥和导入令牌,然后单击关闭。
下载公钥时需要选择公钥格式:
公钥格式选择der格式:下载的公钥文件后缀为.bin(例如:publickey_f240b730-7e3e-4bd7-877f-4fe22524****.bin)。
公钥格式选择pem格式:下载的公钥文件后缀为.pem(例如:publickey_f240b730-7e3e-4bd7-877f-4fe22524****.pem)。
加密密钥材料。
下载公钥和导入令牌后,您可以使用该公钥加密您的密钥材料。
以使用OpenSSL加密密钥材料为例:使用的加密算法需要与获取导入对称密钥材料参数时指定的一致(RSAES_OAEP_SHA_1)。
创建一个密钥材料,使用OpenSSL产生一个32字节的随机数。
openssl rand -out KeyMaterial.bin 32
根据指定的加密算法(RSAES_OAEP_SHA_1)加密密钥材料。
执行下方示例代码时,您需要执行以下操作:
将示例代码中的PublicKey.bin替换为获取导入密钥材料参数中下载的公钥文件的名称。
示例代码以公钥格式为der格式为例,如果您下载公钥文件时选择了pem格式,需要将示例代码中的
-keyform DER
替换为-keyform PEM
。
openssl rsautl -encrypt -in KeyMaterial.bin -oaep -inkey PublicKey.bin -keyform DER -pubin -out EncryptedKeyMaterial.bin
将加密后的密钥材料进行Base64编码,保存为文本文件。
openssl enc -e -base64 -A -in EncryptedKeyMaterial.bin -out EncryptedKeyMaterial_base64.txt
导入密钥材料。
导入令牌与加密密钥材料的公钥具有绑定关系,一个令牌只能为其生成时指定的主密钥导入密钥材料。导入令牌的有效期为24小时,在有效期内可以重复使用,失效以后需要获取新的导入令牌和公钥。
在左侧导航栏,单击用户主密钥。
单击目标密钥ID进入密钥管理页面,密钥材料区域,单击导入密钥材料。
在导入打包后的密钥材料对话框,上传打包后的密钥材料和导入令牌。
打包后的密钥材料:上传加密密钥材料中生成的密钥材料文本文件。
导入令牌:上传获取导入密钥材料参数中获取的导入令牌文本文件。
设置密钥材料过期时间,单击确定。
导入密钥材料成功后,密钥状态从待导入更新为启用中。
通过ALIYUN CLI导入密钥材料
创建外部密钥。
通过命令aliyun kms CreateKey调用CreateKey接口,指定参数Origin为EXTERNAL。
aliyun kms CreateKey --Origin EXTERNAL --Description "External key"
获取导入密钥材料参数。
通过命令aliyun kms GetParametersForImport调用GetParametersForImport接口获取导入密钥材料参数。
aliyun kms GetParametersForImport --KeyId 1339cb7d-54d3-47e0-b595-c7d3dba8**** --WrappingAlgorithm RSAES_OAEP_SHA_1 --WrappingKeySpec RSA_2048
导入密钥材料。
使用公钥对密钥材料进行加密。
公钥是一个2048比特的RSA公钥,使用的加密算法需要与获取导入密钥材料参数时指定的一致。由于API返回的公钥经过Base64编码,因此在使用时需要先进行Base64解码。目前KMS支持的加密算法有RSAES_OAEP_SHA_1、RSAES_OAEP_SHA_256和RSAES_PKCS1_V1_5。
将加密后的密钥材料进行Base64编码。
通过命令aliyun kms ImportKeyMaterial调用ImportKeyMaterial接口,将编码后的密钥材料与导入令牌一起,作为ImportKeyMaterial接口的参数导入KMS。
aliyun kms ImportKeyMaterial --KeyId 1339cb7d-54d3-47e0-b595-c7d3dba8**** --EncryptedKeyMaterial xxx --ImportToken xxxx --KeyMaterialExpireUnix xxxx
通过SDK导入密钥材料
代码示例:
JAVA SDK
说明阿里云账号AccessKey拥有所有API的访问权限,建议您使用RAM用户进行API访问或日常运维。强烈建议不要把AccessKey ID和AccessKey Secret保存到工程代码里,否则可能导致AccessKey泄露,威胁您账号下所有资源的安全。
本示例以将AccessKey配置在环境变量ALIBABA_CLOUD_ACCESS_KEY_ID和ALIBABA_CLOUD_ACCESS_KEY_SECRET的方式来实现身份验证为例。更多认证信息配置方式,请参见Credentials 设置。
//使用最新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 = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID"); String accessKeySecret = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET"); KmsClient kmsclient = new KmsClient(regionId,accessKeyId,accessKeySecret); //创建外部密钥。 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) { //... 省略。 } } }
Go SDK
说明阿里云账号AccessKey拥有所有API的访问权限,建议您使用RAM用户进行API访问或日常运维。强烈建议不要把AccessKey ID和AccessKey Secret保存到工程代码里,否则可能导致AccessKey泄露,威胁您账号下所有资源的安全。
本示例以将AccessKey配置在环境变量ALIBABA_CLOUD_ACCESS_KEY_ID和ALIBABA_CLOUD_ACCESS_KEY_SECRET的方式来实现身份验证为例。更多认证信息配置方式,请参见客户端。
package main import ( "crypto/rand" "crypto/rsa" "crypto/sha256" "crypto/x509" "encoding/base64" "fmt" "log" random "math/rand" "time" "github.com/aliyun/alibaba-cloud-sdk-go/services/kms" ) //KMS CreateKey API封装。 func kmsCreateKey(client *kms.Client) (string, error) { request := kms.CreateCreateKeyRequest() request.Scheme = "https" request.Origin = "EXTERNAL" //创建外部密钥。 response, err := client.CreateKey(request) if err != nil { return "", fmt.Errorf("CreateKey error:%v", err) } return response.KeyMetadata.KeyId, nil } //KMS GetParametersForImport API封装。 func kmsGetParametersForImport(client *kms.Client, keyId, wrappingKeySpec, wrappingAlgorithm string) (string, string, error) { request := kms.CreateGetParametersForImportRequest() request.Scheme = "https" request.KeyId = keyId request.WrappingKeySpec = wrappingKeySpec request.WrappingAlgorithm = wrappingAlgorithm response, err := client.GetParametersForImport(request) if err != nil { return "", "", fmt.Errorf("GetParametersForImport error:%v", err) } return response.PublicKey, response.ImportToken, nil } //KMS ImportKeyMaterial API封装。 func kmsImportKeyMaterial(client *kms.Client, keyId, importToken, encryptedKeyMaterial string) error { request := kms.CreateImportKeyMaterialRequest() request.Scheme = "https" request.KeyId = keyId request.ImportToken = importToken request.EncryptedKeyMaterial = encryptedKeyMaterial _, err := client.ImportKeyMaterial(request) if err != nil { return fmt.Errorf("ImportKeyMaterial error:%v", err) } return nil } func randBytes(n int) []byte { var r = random.New(random.NewSource(time.Now().UnixNano())) bytes := make([]byte, n) for i := range bytes { bytes[i] = byte(r.Intn(256)) } return bytes } func main() { accessKeyId := os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_ID") accessKeySecret := os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET") regionId := "cn-hangzhou" client, err := kms.NewClientWithAccessKey(regionId, accessKeyId, accessKeySecret) if err != nil { log.Fatalf("NewClientWithAccessKey error:%+v\n", err) } //创建一个外部密钥。 keyId, err := kmsCreateKey(client) if err != nil { log.Fatalf("kmsCreateKey error:%+v\n", err) } //以下示例代码产生一个32字节随机数。在实际应用中您需要通过用户的密钥管理系统生成密钥,并使用导入密钥材料参数中的公钥进行加密。 keyMaterial := randBytes(32) //获取导入密钥材料参数。 encryptPublicKey, importToken, err := kmsGetParametersForImport(client, keyId, "RSA_2048", "RSAES_OAEP_SHA_256") if err != nil { log.Fatalf("kmsGetParametersForImport error:%v\n", err) } //Base64解码公钥。 publicKeyDer, err := base64.StdEncoding.DecodeString(encryptPublicKey) if err != nil { log.Fatalf("base64.StdEncoding.DecodeString error:%v\n", err) } //解析成RSA的公钥。 publicKey, err := x509.ParsePKIXPublicKey(publicKeyDer) if err != nil { log.Fatalf("x509.ParsePKIXPublicKey error:%v\n", err) } //加密密钥材料。 cipherDer, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, publicKey.(*rsa.PublicKey), keyMaterial, nil) if err != nil { log.Fatalf("rsa.EncryptOAEP error:%v\n", err) } //加密后的密钥材料,需要进行Base64编码。 encryptedKeyMaterial := base64.StdEncoding.EncodeToString(cipherDer) //导入密钥材料。 err = kmsImportKeyMaterial(client, keyId, importToken, encryptedKeyMaterial) if err != nil { log.Fatalf("ImportKeyMaterial error:%v", err) } }