您可以将源环境(通常为线下的密钥管理设施KMI,或者线下的硬件安全模块HSM)生成的非对称密钥导入专属KMS标准版实例。本文介绍如何将非对称密钥材料导入专属KMS标准版实例。
背景信息
您可以调用DescribeKey接口判断非对称密钥材料来源。当Origin为EXTERNAL时,说明非对称密钥材料由外部导入,称为外部密钥。
当您使用由外部导入的密钥材料时,需要注意以下几点:
请确保您使用了符合要求的随机源生成非对称密钥材料。
每个CMK只能拥有一个非对称密钥材料。一个非对称密钥材料导入CMK后,CMK将与该非对称密钥材料绑定,后续将无法导入其他非对称密钥材料。
导入非对称密钥材料后,暂不支持调用DeleteKeyMaterial接口删除非对称密钥材料。如果您需要删除密钥材料,请调用ScheduleKeyDeletion接口,等待您指定的计划删除时间后,密码机集群中的非对称密钥材料会随着CMK一起被删除。
步骤一:创建外部密钥
导入非对称密钥材料前,您需要先在专属KMS中创建一个密钥材料来源为外部的非对称密钥。创建外部密钥成功后,您可以根据密钥类型选择导入SM2、RSA或ECC密钥材料。
登录密钥管理服务控制台。
在页面左上角的地域下拉列表,选择专属KMS标准版实例所在的地域。
在左侧导航栏,单击专属KMS。
单击目标专属KMS标准版实例操作列的管理。
在用户主密钥区域,单击创建密钥并完成各配置项的配置。
支持的非对称密钥类型为:RSA_2048、RSA_3072、RSA_4096、EC_P256、EC_P256K和EC_SM2。
高级选项中密钥材料来源选择外部。
勾选我了解使用外部密钥材料的方法和意义,然后单击确定。
步骤二:从专属KMS获取导入非对称密钥材料的参数
导入非对称密钥材料的参数包括一个用于加密非对称密钥材料的公钥(IWKpub),以及一个导入令牌。
在左侧导航栏,单击专属KMS。
单击目标专属KMS标准版实例操作列的管理。
在用户主密钥区域,单击目标密钥ID,进入密钥管理页面。
在密钥材料区域,单击获取导入参数。
在获取导入密钥材料的参数对话框,选择公钥类型和加密算法,然后单击下一步。
下载公钥和导入令牌,然后单击关闭。
步骤三:在源环境生成非对称密钥材料
在生成非对称密钥过程中会使用到目标非对称密钥私钥(TAKpriv)、加密公钥(IWKpub)、瞬时密钥(ESK),相关说明如下。
密钥 | 描述 | 提供者 | 标记说明 |
TAK(Target Asymmetric Key) | 待导入的目标非对称密钥。 | 源环境的系统或者工具(如线下的密钥管理设施KMI,或者线下的硬件安全模块HSM)。 |
|
IWK(Import Wrapping Key) | 用于导入TAK的加密密钥。 | 阿里云专属KMS标准版实例。 |
|
ESK(Ephemeral Symmetric Key) | 一个瞬时存在的对称密钥,用于直接加密TAKpriv。 | 源环境的系统或者工具,在完成对TAK的导出操作后立即销毁。 | ESK:对称算法的瞬时密钥。 |
创建一个目标非对称密钥私钥(TAKpriv),密钥类型与步骤一:创建外部密钥时选择的密钥类型一致。
说明TAKpriv格式需要遵循:RSA私钥根据RFC3447进行编码,ECC私钥根据RFC5915进行编码。随后根据RFC5208包装为PKCS#8格式。
创建一个瞬时密钥(ESK)。
使用步骤二:从专属KMS获取导入非对称密钥材料的参数中下载的公钥(IWKpub)加密ESK,得到瞬时对称密钥密文Cipher(ESK)。
使用ESK加密目标非对称密钥私钥TAKpriv,得到目标非对称密钥的私钥密文Cipher(TAKpriv)。
组装结果数据,获取加密密钥材料,组装格式为Cipher(ESK) || Cipher(TAKpriv)。
步骤四:导入非对称密钥材料
导入密钥材料参数包括一个用于加密密钥材料的公钥,以及一个导入令牌。导入令牌与加密密钥材料的公钥具有绑定关系,一个令牌只能为其生成时指定的主密钥导入密钥材料。导入令牌的有效期为24小时,在有效期内可以重复使用,失效以后需要获取新的导入令牌和加密公钥。
在左侧导航栏,单击专属KMS。
单击目标专属KMS标准版实例操作列的管理。
在用户主密钥区域,单击目标密钥ID,进入密钥管理页面。
在密钥材料区域,单击获取导入参数。
在导入打包后的密钥材料对话框,上传打包后的密钥材料和导入令牌后,单击确定。
导入对称密钥材料成功后,密钥状态从待导入更新为启用中。
打包后的密钥材料:上传步骤三:在源环境生成非对称密钥材料中生成的非对称密钥材料文本文件。
导入令牌:上传步骤二:从专属KMS获取导入非对称密钥材料的参数中下载的导入令牌文本文件。
代码示例
阿里云账号AccessKey拥有所有API的访问权限,建议您使用RAM用户进行API访问或日常运维。强烈建议不要把AccessKey ID和AccessKey Secret保存到工程代码里,否则可能导致AccessKey泄露,威胁您账号下所有资源的安全。
本示例以将AccessKey配置在环境变量ALIBABA_CLOUD_ACCESS_KEY_ID和ALIBABA_CLOUD_ACCESS_KEY_SECRET的方式来实现身份验证为例。更多认证信息配置方式,请参见Credentials 设置。
通过KMS Java SDK在专属KMS中创建并导入SM2、ECC和RSA密钥的代码示例如下:
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) {//这里会生成NUllPointer。
throw e;
} catch (ClientException e) {
expStr = e.toString();
if (expStr.contains("SDK.ServerUnreachable")) {//need retry
continue;
}
throw e;
}
}
throw new ClientException(expStr);
}
private byte[] generateEphemeralSymmetricKey(String ephemeralSymmetricKeySpec) throws Exception {
int ephemeralSymmetricKeyLength = 32; //瞬时对称密钥是AES_256时,长度为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;
}
}