当需要在本地离线加解密数据,且希望密钥由 KMS 集中托管和保护时,可以使用托管数据密钥功能。托管数据密钥持久存储于 KMS,可随时通过控制台或 API 获取密钥明文,完成本地数据加解密操作。
工作原理
托管数据密钥基于 KMS 凭据管家实现,将数据密钥存储至 KMS 凭据中,后续使用时直接从凭据中获取。使用流程如下:
创建数据密钥时,KMS 会使用配置的加密主密钥对数据密钥进行加密,生成数据密钥密文。
说明导入数据密钥时,需要手动对数据密钥材料进行加密,以生成数据密钥密文。
生成数据密钥密文后,系统会自动创建一个凭据(数据密钥凭据),将数据密钥密文存储至该凭据中。
使用 SDK 调用 GetManagedDataKey 接口时,KMS 会从数据密钥凭据中取出数据密钥密文,使用加密主密钥解密后返回数据密钥明文。
在本地使用获取到的数据密钥明文进行加解密操作。
与 GenerateDataKey 接口产生的一次性数据密钥不同,托管数据密钥可反复获取明文,适用于需要持久化密钥的离线加解密场景。
创建或导入的数据密钥,生成的数据密钥凭据,仅消耗数据密钥的额度,不消耗凭据的额度。
适用范围
使用托管数据密钥功能前,确认满足以下条件:
KMS 实例类型:仅预付费实例支持。
说明按量付费实例以及跨地域同步实例(副本实例)均不支持。
实例状态:KMS 实例已连接且未过期。
配额限制:实例的数据密钥数量未达到上限。如已达上限,需先购买数据密钥配额。
网关限制:目前仅支持通过共享网关调用。
创建或导入数据密钥
KMS 提供两种方式获取托管数据密钥:
方式 | 密钥材料来源 | 是否支持自动轮转 | 适用场景 |
创建 | KMS 自动生成 | 支持 | 无自有密钥材料,需要 KMS 全托管并支持定期轮转。 |
导入 | 用户自行提供 | 不支持 | 已有密钥材料,需要自主控制密钥来源。 |
创建托管数据密钥
创建数据密钥时,KMS 自动生成密钥材料并用指定的加密主密钥进行保护。创建的数据密钥支持配置自动轮转策略。
登录密钥管理服务控制台,在顶部菜单栏选择地域后,在左侧导航栏单击。
在数据密钥页签,选择目标 KMS 实例后,单击创建数据密钥。
在创建数据密钥面板,配置以下参数。
参数
说明
数据密钥名称
数据密钥的唯一标识名称。最大长度 128 字符。
数据密钥字节数
数据密钥的长度,取值范围 1~1024 字节,默认值 32。常用长度说明如下:
16 字节:适用于 AES-128、SM4。
24 字节:适用于 AES-192。
32 字节:适用于 AES-256。
主密钥类型
选择用于保护数据密钥材料的加密主密钥来源。
选择已有主密钥:从当前实例的对称主密钥列表选择。
创建新的主密钥:系统自动为当前实例创建一个新的对称主密钥。
说明可在页签下查看和管理创建的密钥。
加密主密钥
当主密钥类型为选择已有主密钥时显示。支持从下拉列表搜索并选择同实例的用户主密钥,也可单击高级查询以表格方式选择。
重要仅支持选择对称密钥。
单击确定,完成创建。
导入托管数据密钥(不推荐)
如果需要使用自有密钥材料,可以将密钥材料导入 KMS 进行托管,导入的数据密钥不支持自动轮转。
步骤一:处理密钥材料
对原始密钥材料进行 Base64 编码。
使用加密主密钥加密编码后的密钥材料:
在页签,确定用于保护数据密钥材料的加密主密钥,并记录其密钥 ID。
调用 Encrypt 接口,对 Base64 编码结果加密,得到密钥材料密文。具体操作,请参见加密解密示例。
步骤二:配置数据密钥
登录密钥管理服务控制台,在顶部菜单栏选择地域后,在左侧导航栏单击。
在数据密钥页签,选择目标 KMS 实例后,单击导入数据密钥。
在导入数据密钥面板,配置以下参数。
参数
说明
数据密钥名称
数据密钥的唯一标识名称。最大长度 128 字符。
数据密钥字节数
数据密钥的长度,取值范围 1~1024 字节,默认值 32。常用长度说明如下:
16 字节:适用于 AES-128、SM4。
24 字节:适用于 AES-192。
32 字节:适用于 AES-256。
加密主密钥
选择用于保护数据密钥材料的加密主密钥,仅从当前实例的对称主密钥列表选择。需和步骤一中用于加密的用户主密钥保持一致。
说明可在页签下查看和管理创建的密钥。
数据密钥内容
导入步骤一中获取到的密钥材料密文。
单击确定,完成导入。
应用接入
应用可以通过 API 获取托管数据密钥的明文,完成本地数据加解密。目前仅支持通过共享网关调用相关 API。
相关 API:
GetManagedDataKey:获取托管数据密钥明文。
ListManagedDataKeyVersions:查询托管数据密钥的版本列表。
步骤一:配置权限
应用通过 API 操作托管数据密钥时,需要为应用的 RAM 身份配置相应权限。以下是最小权限策略示例。
获取数据密钥明文和查询版本的权限
{
"Version": "1",
"Statement": [
{
"Effect": "Allow",
"Action": [
"kms:ListManagedDataKeyVersions",
"kms:GetSecretValue",
"kms:GetManagedDataKey"
],
"Resource": [
"acs:kms:{regionId}:{aliuid}:secret/kms-datakey!{dataKeyName}",
"acs:kms:{regionId}:{aliuid}:secret/kms-datakeyversion!{dataKeyName}!*"
]
}
]
}解密数据密钥材料的权限
获取数据密钥明文时,KMS 需要使用加密主密钥解密密钥材料,因此应用还需要对应 CMK 的 Decrypt 权限。
{
"Version": "1",
"Statement": [
{
"Effect": "Allow",
"Action": [
"kms:Decrypt"
],
"Resource": [
"acs:kms:{regionId}:{aliuid}:key/{keyId}"
]
}
]
}参数说明
{regionId}:KMS 实例所在地域 ID,例如cn-hangzhou。{aliuid}:阿里云账号 ID。{dataKeyName}:数据密钥名称。Resource 中第一行为主凭据权限,第二行通过通配符*授权所有版本凭据。{keyId}:加密主密钥的 ID。
通过更改 {dataKeyName} 可以为不同应用授权不同的托管数据密钥,实现细粒度的权限隔离。
步骤二:使用 SDK 接入
通过 KMS SDK 调用 GetManagedDataKey 接口获取数据密钥明文,在本地完成数据加解密。以下以 Go SDK 为例,展示如何使用 AES-GCM 算法进行数据加解密。如需手动下载数据密钥明文,请参见本文手动下载数据密钥明文章节。
加密数据
加密时获取最新版本的数据密钥进行加密,并将版本标识附加在密文头部,便于解密时定位密钥版本。
package main
import (
"crypto/aes"
"crypto/cipher"
cryptorand "crypto/rand"
"encoding/base64"
"io"
"os"
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
kmsclient "github.com/alibabacloud-go/kms-20160120/v4/client"
"github.com/alibabacloud-go/tea/tea"
)
func EncryptWithManagedDataKey(plaintext []byte, dataKeyName string) ([]byte, error) {
kms, err := kmsclient.NewClient(&openapi.Config{
AccessKeyId: tea.String(os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")),
AccessKeySecret: tea.String(os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")),
Endpoint: tea.String("kms.cn-hangzhou.aliyuncs.com"),
})
if err != nil {
return nil, err
}
// 获取最新版本的托管数据密钥明文,也可手动下载密钥明文
getResp, err := kms.GetManagedDataKey(&kmsclient.GetManagedDataKeyRequest{
DataKeyName: tea.String(dataKeyName),
UseLatest: tea.Bool(true),
})
if err != nil {
return nil, err
}
dataKeyVersionId := tea.StringValue(getResp.Body.DataKeyVersionId)
b64PlainDataKey := tea.StringValue(getResp.Body.Plaintext)
//base64转码
plainDataKey, err := base64.StdEncoding.DecodeString(b64PlainDataKey)
if err != nil {
return nil, err
}
// 使用 AES-GCM 加密数据
block, err := aes.NewCipher(plainDataKey)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonce := make([]byte, gcm.NonceSize())
_, err = io.ReadFull(cryptorand.Reader, nonce)
if err != nil {
return nil, err
}
ciphertext := gcm.Seal(nonce, nonce, plaintext, nil)
// 将版本标识(固定 8 字节)和密文拼接,便于解密时定位密钥版本
output := append([]byte(dataKeyVersionId), ciphertext...)
return output, nil
}解密数据
解密时从密文头部解析版本标识,获取对应版本的数据密钥明文进行解密。
package main
import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"fmt"
"os"
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
kmsclient "github.com/alibabacloud-go/kms-20160120/v4/client"
"github.com/alibabacloud-go/tea/tea"
)
func DecryptWithManagedDataKey(encrypted []byte, dataKeyName string) ([]byte, error) {
kms, err := kmsclient.NewClient(&openapi.Config{
AccessKeyId: tea.String(os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")),
AccessKeySecret: tea.String(os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")),
Endpoint: tea.String("kms.cn-hangzhou.aliyuncs.com"),
})
if err != nil {
return nil, err
}
// 从密文头部解析版本标识(前 8 字节)
dataKeyVersionId := string(encrypted[:8])
ciphertext := encrypted[8:]
// 获取对应版本的托管数据密钥明文
getResp, err := kms.GetManagedDataKey(&kmsclient.GetManagedDataKeyRequest{
DataKeyName: tea.String(dataKeyName),
DataKeyVersionId: tea.String(dataKeyVersionId),
})
if err != nil {
return nil, err
}
b64PlainDataKey := tea.StringValue(getResp.Body.Plaintext)
plainDataKey, err := base64.StdEncoding.DecodeString(b64PlainDataKey)
if err != nil {
return nil, err
}
// 使用 AES-GCM 解密数据
block, err := aes.NewCipher(plainDataKey)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonceSize := gcm.NonceSize()
if len(ciphertext) < nonceSize {
return nil, fmt.Errorf("ciphertext too short")
}
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
return gcm.Open(nil, nonce, ciphertext, nil)
}设置自动轮转策略(可选)
自动轮转会定期生成新版本的密钥材料,旧版本保留可用。
导入的数据密钥不支持轮转。
登录密钥管理服务控制台,在顶部菜单栏选择地域后,在左侧导航栏单击。
在数据密钥页签,选择目标 KMS 实例后,单击目标数据密钥的名称,进入详情页。
在版本列表区域,单击设置轮转。参考如下说明,完成配置后,单击确定。
参数
说明
自动轮转
开启或关闭自动轮转。
轮转周期
轮转周期,取值范围 7~365 天,默认值 7 天。
警告数据密钥轮转一次,消耗一次数据密钥的额度,请合理规划轮转策略,避免不必要的额度消耗。
其他操作
手动下载数据密钥明文
通过控制台下载数据密钥的明文材料,用于本地离线加解密。启用自动轮转后,数据密钥存在多个版本,从列表下载默认获取最新版本,如需下载历史版本可进入详情页的版本列表操作。
下载的材料为 Base64 编码格式,需要先解码后才能用于加解密操作。
从数据密钥列表下载
下载单个数据密钥明文:
在数据密钥页签,选择目标 KMS 实例后,单击目标数据密钥的操作列的下载明文。
浏览器自动下载明文文件,文件名格式为
PlainDataKey_{数据密钥名称}_{版本ID}.txt。
批量下载:
在数据密钥页签,选择目标 KMS 实例后,勾选多个数据密钥。
单击列表下方的批量下载明文。
系统将所有选中密钥的最新版本明文打包为 ZIP 文件下载(文件名格式:
dataKeys_{地域ID}_{时间戳}.zip)。
从数据密钥详情页下载
通过数据密钥详情页,可以选择任意版本下载明文材料。
在数据密钥页签,选择目标 KMS 实例后,单击目标数据密钥的名称,进入详情页。
在版本列表区域,单击目标版本操作列的下载明文。
说明版本列表中的所有版本数据,均可以使用。
计划删除数据密钥
删除数据密钥为不可逆操作。为防止误删,KMS 采用计划删除机制,在预删除周期内可随时撤销删除。预删除周期结束后,数据密钥将被永久删除。
数据密钥删除后无法恢复,使用该密钥加密的数据将无法解密,请在删除前确认不再需要该密钥。
执行计划删除
在数据密钥页签,选择目标 KMS 实例后,单击目标数据密钥操作列的计划删除。
在对话框中,设置预删除周期(7~30 天,默认 30 天)后,单击确定。
撤销计划删除
预删除周期内,可以随时撤销删除操作,恢复数据密钥的正常状态。
在数据密钥页签,选择目标 KMS 实例后,找到处于计划删除状态的数据密钥。
单击目标数据密钥操作列的撤销删除。
在确认对话框中,单击确定。
查看版本和轮转信息
登录密钥管理服务控制台,在顶部菜单栏选择地域后,在左侧导航栏单击。
在数据密钥页签,选择目标 KMS 实例后,单击目标数据密钥的名称,进入详情页。
在版本列表列表区域可查看轮转后的各版本信息。
在基础信息区域可查看上次轮转时间、下次轮转时间、轮转周期等。