加解密小量数据
场景说明
当有少量数据(例如:口令、证书、电话号码等)需要加解密时,用户可以通过密钥管理服务(Key Management Service,KMS)界面使用在线工具加解密数据,或者调用KMS的API接口使用指定的用户主密钥直接加密、解密数据。
约束条件
当前支持不大于4KB的小数据加解密。
在线工具加解密
步骤 1 单击目标自定义密钥的别名,进入密钥详细信息在线工具加密数据页面。
步骤 2 在“加密”文本框中输入待加密的数据。
步骤 3 单击“执行”,右侧文本框显示加密后的密文数据。
解密数据
说明加密数据时,使用当前指定的密钥加密数据。
用户可单击“清除”,清除已输入的数据。
用户可单击“复制到剪切板”拷贝加密后的密文数据,并保存到本地文件中。
步骤 4 解密数据时,可单击任意“启用”状态的非默认密钥别名,进入该密钥的在线工具页面。
步骤 5 单击“解密”,在左侧文本框中数据待解密的密文数据。
说明在线工具自动识别并使用数据被加密时使用的密钥解密数据。
若该密钥已被删除,会导致解密失败。
步骤 6 单击“执行”,右侧文本框中显示解密后的明文数据。
说明用户可直接单击“复制到剪切板”拷贝解密后的明文数据,并保存到本地文件中。
调用API接口加解密
以保护服务器HTTPS证书为例,采用调用KMS的API接口方式进行说明,如图所示。
保护服务器HTTPS证书
流程说明如下:
- 用户需要在KMS中创建一个用户主密钥。
- 用户调用KMS的“encrypt-data”接口,使用指定的用户主密钥将明文证书加密为密文证书。
- 用户在服务器上部署密文证书。
- 当服务器需要使用证书时,调用KMS的“decrypt-data”接口,将密文证书解密为明文证书。
由于控制台输入的加密原文会经过一次Base64转码后才传至后端,所以当调用API接口解密密文的时候,返回的明文就是加密原文经过Base64转码得到的字符串,故API加密密文后需要调用API进行解密,若使用控制台解密API加密密文则会产生乱码。
加解密大量数据
场景说明
当有大量数据(例如:照片、视频或者数据库文件等)需要加解密时,用户可采用信封加密方式加解密数据,无需通过网络传输大量数据即可完成数据加解密。
加密和解密原理
大量数据加密
说明用户需要在KMS中创建一个用户主密钥。
用户调用KMS的“create-datakey”接口创建数据加密密钥。用户得到一个明文的数据加密密钥和一个密文的数据加密密钥。其中密文的数据加密密钥是由指定的用户主密钥加密明文的数据加密密钥生成的。
用户使用明文的数据加密密钥来加密明文文件,生成密文文件。
用户将密文的数据加密密钥和密文文件一同存储到持久化存储设备或服务中。
大量数据解密
说明用户从持久化存储设备或服务中读取密文的数据加密密钥和密文文件。
用户调用KMS的“decrypt-datakey”接口,使用对应的用户主密钥(即生成密文的数据加密密钥时所使用的用户主密钥)来解密密文的数据加密密钥,取得明文的数据加密密钥。
若对应的用户主密钥被误删除,会导致解密失败。因此,需要妥善管理好用户主密钥。
用户使用明文的数据加密密钥来解密密文文件。
加密和解密的API
您可以调用以下API,在本地对数据进行加解密。
API名称 | 说明 |
---|---|
创建数据密钥 | 创建数据密钥。 |
解密数据密钥 | 用指定的主密钥解密数据密钥。 |
加密本地文件
步骤1 通过控制台,创建用户主密钥。
步骤2 请准备基础认证信息。
- ACCESS_KEY: 帐号Access Key
- SECRET_ACCESS_KEY: 帐号Secret Access Key
- PROJECT_ID:项目ID
- KMS_ENDPOINT: KMS服务访问终端地址。
步骤3 加密本地文件。
示例代码中:
- 用户主密钥:控制台创建的密钥ID。
- 明文数据文件:FirstPlainFile.jpg。
- 输出的密文数据文件:SecondEncryptFile.jpg。
import com.ctyun.sdk.core.auth.BasicCredentials;
import com.ctyun.sdk.kms.v1.KmsClient;
import com.ctyun.sdk.kms.v1.model.CreateDatakeyRequest;
import com.ctyun.sdk.kms.v1.model.CreateDatakeyRequestBody;
import com.ctyun.sdk.kms.v1.model.CreateDatakeyResponse;
import com.ctyun.sdk.kms.v1.model.DecryptDatakeyRequest;
import com.ctyun.sdk.kms.v1.model.DecryptDatakeyRequestBody;
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.security.SecureRandom;
/**
* 使用数据密钥(DEK)进行文件加解密
* 激活assert语法,请在VM_OPTIONS中添加-ea
*/
public class FileStreamEncryptionExample {
private static final String ACCESS_KEY = "<AccessKey>";
private static final String SECRET_ACCESS_KEY = "<SecretAccessKey>";
private static final String PROJECT_ID = "<ProjectID>";
private static final String KMS_ENDPOINT = "<KmsEndpoint>";
// KMS服务接口版本信息,当前固定为v1.0
private static final String KMS_INTERFACE_VERSION = "v1.0";
/**
* AES算法相关标识:
* - AES_KEY_BIT_LENGTH: AES256密钥比特长度
* - AES_KEY_BYTE_LENGTH: AES256密钥字节长度
* - AES_ALG: AES256算法,本例分组模式使用GCM,填充使用PKCS5Padding
* - AES_FLAG: AES算法标识
* - GCM_TAG_LENGTH: GCM TAG长度
* - GCM_IV_LENGTH: GCM 初始向量长度
*/
private static final String AES_KEY_BIT_LENGTH = "256";
private static final String AES_KEY_BYTE_LENGTH = "32";
private static final String AES_ALG = "AES/GCM/PKCS5Padding";
private static final String AES_FLAG = "AES";
private static final int GCM_TAG_LENGTH = 16;
private static final int GCM_IV_LENGTH = 12;
public static void main(final String[] args) {
// 您在控制台创建的用户主密钥ID
final String keyId = args[0];
encryptFile(keyId);
}
/**
* 使用数据密钥加解密文件实例
*
* @param keyId 用户主密钥ID
*/
static void encryptFile(String keyId) {
// 1.准备访问认证信息
final BasicCredentials auth = new BasicCredentials().withAk(ACCESS_KEY).withSk(SECRET_ACCESS_KEY)
.withProjectId(PROJECT_ID);
// 2.初始化SDK,传入认证信息及KMS访问终端地址
final KmsClient kmsClient = KmsClient.newBuilder().withCredential(auth).withEndpoint(KMS_ENDPOINT).build();
// 3.组装创建数据密钥请求信息
final CreateDatakeyRequest createDatakeyRequest = new CreateDatakeyRequest().withVersionId(KMS_INTERFACE_VERSION)
.withBody(new CreateDatakeyRequestBody().withKeyId(keyId).withDatakeyLength(AES_KEY_BIT_LENGTH));
// 4.创建数据密钥
final CreateDatakeyResponse createDatakeyResponse = kmsClient.createDatakey(createDatakeyRequest);
// 5.接收创建的数据密钥信息
// 密文密钥与KeyId建议保存在本地,方便解密数据时获取明文密钥
// 明文密钥在创建后立即使用,使用前需要将16进制明文密钥转换成byte数组
final String cipherText = createDatakeyResponse.getCipherText();
final byte[] plainKey = hexToBytes(createDatakeyResponse.getPlainText());
// 6.准备待加密的文件
// inFile 待加密的原文件
// outEncryptFile 加密后的文件
final File inFile = new File("FirstPlainFile.jpg");
final File outEncryptFile = new File("SecondEncryptFile.jpg");
// 7.使用AES算法进行加密时,可以创建初始向量
final byte[] iv = new byte[GCM_IV_LENGTH];
final SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(iv);
// 8.对文件进行加密,并存储加密后的文件
doFileFinal(Cipher.ENCRYPT_MODE, inFile, outEncryptFile, plainKey, iv);
}
/**
* 对文件进行加解密
*
* @param cipherMode 加密模式,可选值为Cipher.ENCRYPT_MODE或者Cipher.DECRYPT_MODE
* @param infile 待加解密的文件
* @param outFile 加解密后的文件
* @param keyPlain 明文密钥
* @param iv 初始化向量
*/
static void doFileFinal(int cipherMode, File infile, File outFile, byte[] keyPlain, byte[] iv) {
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(infile));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(outFile))) {
final byte[] bytIn = new byte[(int) infile.length()];
final int fileLength = bis.read(bytIn);
assert fileLength > 0;
final SecretKeySpec secretKeySpec = new SecretKeySpec(keyPlain, AES_FLAG);
final Cipher cipher = Cipher.getInstance(AES_ALG);
final GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * Byte.SIZE, iv);
cipher.init(cipherMode, secretKeySpec, gcmParameterSpec);
final byte[] bytOut = cipher.doFinal(bytIn);
bos.write(bytOut);
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
}
解密本地文件
步骤1 请准备基础认证信息。
- ACCESS_KEY: 帐号Access Key
- SECRET_ACCESS_KEY: 帐号Secret Access Key
- PROJECT_ID: 项目ID
- KMS_ENDPOINT: KMS服务访问终端地址。
步骤2 解密本地文件。
示例代码中:
- 用户主密钥:控制台创建的密钥ID。
- 输出的密文数据文件:SecondEncryptFile.jpg。
- 加密后再解密的数据文件:ThirdDecryptFile.jpg。
import com.ctyun.sdk.core.auth.BasicCredentials;
import com.ctyun.sdk.kms.v1.KmsClient;
import com.ctyun.sdk.kms.v1.model.CreateDatakeyRequest;
import com.ctyun.sdk.kms.v1.model.CreateDatakeyRequestBody;
import com.ctyun.sdk.kms.v1.model.CreateDatakeyResponse;
import com.ctyun.sdk.kms.v1.model.DecryptDatakeyRequest;
import com.ctyun.sdk.kms.v1.model.DecryptDatakeyRequestBody;
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.security.SecureRandom;
/**
* 使用数据密钥(DEK)进行文件加解密
* 激活assert语法,请在VM_OPTIONS中添加-ea
*/
public class FileStreamEncryptionExample {
private static final String ACCESS_KEY = "<AccessKey>";
private static final String SECRET_ACCESS_KEY = "<SecretAccessKey>";
private static final String PROJECT_ID = "<ProjectID>";
private static final String KMS_ENDPOINT = "<KmsEndpoint>";
// KMS服务接口版本信息,当前固定为v1.0
private static final String KMS_INTERFACE_VERSION = "v1.0";
/**
* AES算法相关标识:
* - AES_KEY_BIT_LENGTH: AES256密钥比特长度
* - AES_KEY_BYTE_LENGTH: AES256密钥字节长度
* - AES_ALG: AES256算法,本例分组模式使用GCM,填充使用PKCS5Padding
* - AES_FLAG: AES算法标识
* - GCM_TAG_LENGTH: GCM TAG长度
* - GCM_IV_LENGTH: GCM 初始向量长度
*/
private static final String AES_KEY_BIT_LENGTH = "256";
private static final String AES_KEY_BYTE_LENGTH = "32";
private static final String AES_ALG = "AES/GCM/PKCS5Padding";
private static final String AES_FLAG = "AES";
private static final int GCM_TAG_LENGTH = 16;
private static final int GCM_IV_LENGTH = 12;
public static void main(final String[] args) {
// 您在控制台创建的用户主密钥ID
final String keyId = args[0];
// 创建数据密钥时,响应的密文数据密钥
final String cipherText = args[1];
decryptFile(keyId, cipherText);
}
/**
* 使用数据密钥加解密文件实例
*
* @param keyId 用户主密钥ID
* @param cipherText 密文数据密钥
*/
static void decryptFile(String keyId,String cipherText) {
// 1.准备访问认证信息
final BasicCredentials auth = new BasicCredentials().withAk(ACCESS_KEY).withSk(SECRET_ACCESS_KEY)
.withProjectId(PROJECT_ID);
// 2.初始化SDK,传入认证信息及KMS访问终端地址
final KmsClient kmsClient = KmsClient.newBuilder().withCredential(auth).withEndpoint(KMS_ENDPOINT).build();
// 3.准备待加密的文件
// inFile 待加密的文件
// outEncryptFile 加密后的文件
// outDecryptFile 加密后再解密的文件
final File inFile = new File("FirstPlainFile.jpg");
final File outEncryptFile = new File("SecondEncryptFile.jpg");
final File outDecryptFile = new File("ThirdDecryptFile.jpg");
// 4.使用AES算法进行解密时,初始向量需要与加密时保持一致,此处仅为占位。
final byte[] iv = new byte[GCM_IV_LENGTH];
// 5.组装解密数据密钥的请求,其中cipherText为创建数据密钥时返回的密文数据密钥。
final DecryptDatakeyRequest decryptDatakeyRequest = new DecryptDatakeyRequest()
.withVersionId(KMS_INTERFACE_VERSION).withBody(new DecryptDatakeyRequestBody()
.withKeyId(keyId).withCipherText(cipherText).withDatakeyCipherLength(AES_KEY_BYTE_LENGTH));
// 6.解密数据密钥,并对返回的16进制明文密钥换成byte数组
final byte[] decryptDataKey = hexToBytes(kmsClient.decryptDatakey(decryptDatakeyRequest).getDataKey());
// 7.对文件进行解密,并存储解密后的文件
// 句末的iv为加密示例中创建的初始向量
doFileFinal(Cipher.DECRYPT_MODE, outEncryptFile, outDecryptFile, decryptDataKey, iv);
// 8.比对原文件和加密后再解密的文件
assert getFileSha256Sum(inFile).equals(getFileSha256Sum(outDecryptFile));
}
/**
* 对文件进行加解密
*
* @param cipherMode 加密模式,可选值为Cipher.ENCRYPT_MODE或者Cipher.DECRYPT_MODE
* @param infile 待加解密的文件
* @param outFile 加解密后的文件
* @param keyPlain 明文密钥
* @param iv 初始化向量
*/
static void doFileFinal(int cipherMode, File infile, File outFile, byte[] keyPlain, byte[] iv) {
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(infile));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(outFile))) {
final byte[] bytIn = new byte[(int) infile.length()];
final int fileLength = bis.read(bytIn);
assert fileLength > 0;
final SecretKeySpec secretKeySpec = new SecretKeySpec(keyPlain, AES_FLAG);
final Cipher cipher = Cipher.getInstance(AES_ALG);
final GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * Byte.SIZE, iv);
cipher.init(cipherMode, secretKeySpec, gcmParameterSpec);
final byte[] bytOut = cipher.doFinal(bytIn);
bos.write(bytOut);
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
/**
* 十六进制字符串转byte数组
*
* @param hexString 十六进制字符串
* @return byte数组
*/
static byte[] hexToBytes(String hexString) {
final int stringLength = hexString.length();
assert stringLength > 0;
final byte[] result = new byte[stringLength / 2];
int j = 0;
for (int i = 0; i < stringLength; i += 2) {
result[j++] = (byte) Integer.parseInt(hexString.substring(i, i + 2), 16);
}
return result;
}
/**
* 计算文件SHA256摘要
*
* @param file 文件
* @return SHA256摘要
*/
static String getFileSha256Sum(File file) {
int length;
MessageDigest sha256;
byte[] buffer = new byte[1024];
try {
sha256 = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e.getMessage());
}
try (FileInputStream inputStream = new FileInputStream(file)) {
while ((length = inputStream.read(buffer)) != -1) {
sha256.update(buffer, 0, length);
}
return new BigInteger(1, sha256.digest()).toString(16);
} catch (IOException e) {
throw new RuntimeException(e.getMessage());
}
}
}