当您创建密钥材料来源为外部的密钥时,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)的对称密钥作为密钥材料。
通过控制台导入密钥材料
通过ALIYUN CLI导入密钥材料
通过SDK导入密钥材料
代码示例:
- 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); //创建外部密钥。 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
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 := "*** Provide your AccessKeyId ***" accessKeySecret := "*** Provide your AccessKeySecret ***" 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) } }