Skip to content

cookchen233/mykms

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

自建 KMS(密钥管理服务)

1. 项目背景与目标

本项目旨在自建一套基于 Go 的 KMS(Key Management Service,密钥管理服务),为内部业务提供统一的密钥管理与加解密能力,主要目标包括:

  • 统一管理密钥:集中管理主密钥(Master Key)、业务密钥(Business Key/CMK)、数据密钥(DEK)。
  • 提供标准加密服务接口:对业务系统提供 Encrypt / Decrypt / GenerateDataKey / DecryptDataKey 等接口。
  • 提高安全性:避免业务系统直接持有主密钥,通过密钥分层和受控解锁流程降低密钥泄露风险。
  • 支持主密钥分片模式:通过多合伙人分片输入的方式,控制 KMS 解锁权限;可扩展到实体 U 盘 + 分片方案。

2. 业务范围与场景

KMS 主要面向以下加密场景(可按需要逐步接入):

  • 数据库字段加密:如手机号、身份证号、银行卡号等敏感字段。
  • 文件加密:如存储在对象存储/共享存储中的敏感文件。
  • API 鉴权/签名(可选,后期扩展):对外接口的请求签名与验签。
  • 令牌/票据加密(可选):如 JWT、会话票据等。

密钥类型:

  • 对称密钥:如 AES-256-GCM、SM4 等。
  • 非对称密钥(后期扩展):如 RSA、ECC、SM2 等,用于签名/验签。
  • 分层密钥结构:
    • 主密钥(Master Key / Root Key)。
    • 业务密钥(Business Key / CMK):对应不同业务用途。
    • 数据密钥(DEK):短生命周期,用于实际数据加解密。

3. 整体架构

3.1 管理面(Admin Plane)

管理面主要给安全管理员/运维人员使用,功能包括:

  • 主密钥/业务密钥管理:创建、启用、禁用、轮换、销毁(后续迭代逐步完善)。
  • 调用方(Client/App)管理:为不同业务服务分配 client_id/client_secret 或证书。
  • 访问策略与权限:配置某个 Client 能否对某个 Key 执行 Encrypt/Decrypt/GenerateDataKey 等操作。
  • 审计与日志:查看密钥管理操作和加解密调用日志。
  • 解锁流程:在 Locked 状态下,通过合伙人分片输入解锁 KMS。

管理面将通过 HTTP(S) 管理接口 + 简易管理网页提供能力,仅在内网/管理网络访问。

3.2 数据面(Data Plane)

数据面主要面向业务系统,对外提供 KMS 核心功能接口:

  • Encrypt:加密明文数据,返回密文。
  • Decrypt:解密密文数据,返回明文。
  • GenerateDataKey:生成数据密钥(DEK),返回明文 DEK + 被业务密钥加密过的 DEK 密文。
  • DecryptDataKey:根据 DEK 密文解出明文 DEK,用于本地解密数据。
  • 后续可扩展 Sign / Verify 等签名/验签接口。

业务服务与 KMS 之间通过内部网络通信,KMS 要对每次调用进行身份认证、权限校验与审计记录。

3.3 Go 服务内部分层设计

采用"单服务 + 内部分层清晰"的方式实现,便于后续拆分:

  • 接口层(API / Handler)
    • HTTP Handler,负责解析请求参数、调用应用层服务、返回统一响应。
  • 应用层(App / Service / Usecase)
    • 编排认证、授权、密钥加载、加解密调用、审计记录等多个步骤。
  • 领域层(Domain)
    • 核心业务对象和规则,例如 Key/KeyVersion、Client、Policy、AuditLog 等。
    • 密钥生命周期(创建/轮换/禁用/销毁)与策略校验逻辑。
    • 抽象的加解密接口 CryptoEngine,不依赖具体实现库。
  • 基础设施层(Infra)
    • 存储(DB/Redis)、日志、配置、加解密实现(软实现或对接 HSM)、认证实现、审计日志落地、监控等。

示例目录结构(仅示意):

cmd/
  kms/
    main.go

internal/
  api/
    http/
      router.go
      middleware.go
      handler_kms.go      # Encrypt/Decrypt 等
      handler_admin.go    # 密钥管理、Client 管理、解锁/分片提交等

  app/
    kms/
      encrypt_service.go
      datakey_service.go
    admin/
      key_mgmt_service.go
      client_mgmt_service.go
      policy_service.go
      unlock_service.go   # 解锁与分片收集相关逻辑

  domain/
    entity/
      key.go
      client.go
      policy.go
      audit_log.go
    repo/
      key_repo.go
      client_repo.go
      policy_repo.go
      audit_repo.go
    service/
      key_lifecycle.go
      policy_checker.go
      crypto_engine.go
      shard_collector.go  # 分片收集器抽象

  infra/
    db/
      mysql.go
      key_mysql_repo.go
    cache/
      redis.go
    crypto/
      sw_engine.go
      hsm_engine.go       # 预留对接硬件 HSM
    auth/
      client_auth.go
    audit/
      audit_logger.go
    config/
      config.go
    logger/
      logger.go
    metrics/
      prometheus.go

pkg/
  errors/
  utils/

4. KMS 核心接口

4.1 Encrypt(加密)

  • 用途:使用指定业务密钥(或默认密钥)对明文数据进行加密,返回密文。
  • 建议 HTTP 形式
    • POST /api/kms/encrypt
  • 请求示例
{
  "key_id": "cmk-123456",
  "plaintext": "MTIzNDU2",          
  "algorithm": "AES_256_GCM",
  "aad": "YmluZGVkLWRhdGE=",
  "client_id": "user-service"
}
  • 响应示例
{
  "ciphertext": "BASE64_CIPHERTEXT",
  "key_version": "v3",
  "algorithm": "AES_256_GCM",
  "aad": "YmluZGVkLWRhdGE="
}

明文与密文建议统一使用 Base64 编码进行传输,避免字符集问题。

4.2 Decrypt(解密)

  • 用途:使用指定业务密钥(或根据密文元数据)解密密文数据,返回明文。
  • 建议 HTTP 形式
    • POST /api/kms/decrypt
  • 请求示例
{
  "key_id": "cmk-123456",
  "ciphertext": "BASE64_CIPHERTEXT",
  "algorithm": "AES_256_GCM",
  "aad": "YmluZGVkLWRhdGE=",
  "client_id": "user-service"
}
  • 响应示例
{
  "plaintext": "MTIzNDU2",
  "key_version": "v3",
  "algorithm": "AES_256_GCM"
}

4.3 GenerateDataKey(生成数据密钥)

  • 用途:根据 CMK 生成数据密钥(DEK),用于业务方本地对大量数据加密。
  • 建议 HTTP 形式
    • POST /api/kms/generate-data-key
  • 请求示例
{
  "key_id": "cmk-123456",
  "key_spec": "AES_256",
  "client_id": "user-service"
}
  • 响应示例
{
  "plaintext_data_key": "BASE64_DEK",
  "ciphertext_data_key": "BASE64_ENCRYPTED_DEK",
  "key_version": "v3"
}

业务服务:

  • plaintext_data_key 在内存中对实际业务数据加密。
  • ciphertext_data_key 与密文数据一起存储到数据库或文件元数据中,之后解密数据时再通过 KMS 解出 DEK。

4.4 DecryptDataKey(解 DEK)

  • 用途:根据存储的 DEK 密文解出明文 DEK。
  • 建议 HTTP 形式
    • POST /api/kms/decrypt-data-key
  • 请求示例
{
  "ciphertext_data_key": "BASE64_ENCRYPTED_DEK",
  "client_id": "user-service"
}
  • 响应示例
{
  "plaintext_data_key": "BASE64_DEK",
  "key_id": "cmk-123456",
  "key_version": "v3"
}

生产场景建议优先采用数据密钥模式,而不是每次都直接对业务数据调用 Encrypt/Decrypt 接口。

4.5 调用方接入流程

4.5.1 前置条件

  • KMS 已部署并处于 Unlocked 状态(管理员在 /admin/shard 页面完成解锁)。
  • 已存在至少一个业务密钥(默认会在初始化时创建 key_id = "default" 的业务密钥)。

4.5.2 直接加解密(简单模式)

适用于数据量不大、性能要求一般的场景,例如配置、小量敏感字段。

  • 加密示例
PLAINTEXT="13800000000"  # 明文手机号
B64_PLAIN=$(printf "%s" "$PLAINTEXT" | base64)

curl -s http://localhost:9455/api/kms/encrypt \
  -H 'Content-Type: application/json' \
  -d '{
    "key_id": "default",
    "plaintext": "'"$B64_PLAIN"'",
    "algorithm": "AES_256_GCM"
  }'

返回示例(精简):

{
  "code": 0,
  "data": {
    "ciphertext": "BASE64_CIPHERTEXT",
    "key_version": "v1"
  }
}

业务侧把 BASE64_CIPHERTEXT 存到数据库字段中即可。

  • 解密示例
B64_CIPHERTEXT="BASE64_CIPHERTEXT_FROM_DB"

curl -s http://localhost:9455/api/kms/decrypt \
  -H 'Content-Type: application/json' \
  -d '{
    "key_id": "default",
    "ciphertext": "'"$B64_CIPHERTEXT"'",
    "algorithm": "AES_256_GCM"
  }'

返回示例:

{
  "code": 0,
  "data": {
    "plaintext": "TVQyM0ZEMg=="
  }
}

业务侧再做一次 Base64 解码得到原始明文。

4.5.3 使用数据密钥(推荐模式)

适用于大量数据加解密(如文件、批量记录),减少对 KMS 的压力:

  1. 调用 GenerateDataKey 获取 一次性 DEK
curl -s http://localhost:9455/api/kms/generate-data-key \
  -H 'Content-Type: application/json' \
  -d '{
    "key_id": "default",
    "key_spec": "AES_256"
  }'

返回示例:

{
  "code": 0,
  "data": {
    "plaintext_data_key": "BASE64_DEK",
    "ciphertext_data_key": "BASE64_ENCRYPTED_DEK",
    "key_version": "v1"
  }
}
  1. 业务服务:
  • 本地解出 plaintext_data_key(Base64 解码得到 32 字节 AES key)。
  • 使用该 DEK 在本地对大文件或大量记录进行加密(不需要再调 KMS)。
  • ciphertext_data_key 与业务密文一起存入数据库/对象存储元数据中。
  1. 需要解密时:
  • 从数据库/元数据中取出 ciphertext_data_key
  • 调用 DecryptDataKey 解出明文 DEK:
curl -s http://localhost:9455/api/kms/decrypt-data-key \
  -H 'Content-Type: application/json' \
  -d '{
    "ciphertext_data_key": "BASE64_ENCRYPTED_DEK"
  }'

得到明文 DEK(Base64),本地解码后用来解密业务数据。

4.5.4 服务端集成建议

  • 在业务服务中封装一个 KMS Client(SDK 或简单 HTTP 封装):
    • 提供 EncryptField, DecryptField, GenerateDataKey, DecryptDataKey 等方法。
    • 统一处理 Base64 编码/解码、错误码转换、重试等逻辑。
  • 所有访问 KMS 的逻辑集中在一处,方便未来:
    • 更换 KMS 地址 / 协议。
    • 增加认证(client_id/client_secret、mTLS 等)。
    • 统一打审计日志和监控。

5. 主密钥(Master Key)安全方案

5.1 原则

  • 不在代码、配置文件、环境变量中存储主密钥明文。
  • 服务器本地不持久化主密钥明文,仅可存储被主密钥加密过的业务密钥密文。
  • KMS 服务进程在正常运行时不应长期持有主密钥,仅在启动解锁阶段短暂使用。

5.2 当前阶段方案:接口模拟 U 盘 + 主密钥分片

由于当前不引入实体 U 盘和真实 HSM,采取:

  • 通过内网部署网页操作面板模拟 U 盘插入和主密钥注入过程。
  • 支持将主密钥分片到多名合伙人,服务启动时集齐各合伙人的密钥分片,达到门限后在内存中合成主密钥,完成解锁。

该方案相比直接写死主密钥在一个文件中更安全,且在逻辑上与"物理 U 盘 + 分片"模式相似,未来可直接升级为 U 盘读取。

5.3 KMS 状态机:Locked / Unlocked

定义两种状态:

  • Locked(上锁)
    • 业务接口 Encrypt/Decrypt/GenerateDataKey/DecryptDataKey/... 全部拒绝服务,返回状态码如 KMS_LOCKED
    • 内存中不存在主密钥,也未加载业务密钥明文。
  • Unlocked(解锁)
    • 已通过分片/主密钥注入流程解密并加载业务密钥明文。
    • 业务接口正常可用。

规则:

  • 服务启动后默认为 Locked 状态。
  • 仅在完成合伙人分片输入并成功合成主密钥之后,才进入 Unlocked 状态。
  • 可选支持手动 Lock 操作,将服务重新打回 Locked 状态并清除内存中业务密钥。

5.4 磁盘上的业务密钥存储

  • 磁盘上仅存放被主密钥加密过的业务密钥密文文件,例如:

    • EncMK(BK_USER_DB)
    • EncMK(BK_FILES)
  • 每个业务密钥文件可包括:

    • KeyId
    • 算法与用途
    • 版本号
    • 被主密钥加密过的密文(以及必要的 IV、Tag 等)
  • 不在磁盘上存储主密钥本体。

5.5 解锁流程(软 U 盘 + 分片简化版)

  1. KMS 启动,处于 Locked 状态。
  2. 合伙人访问管理页面 /admin/shard,依次输入各自的密钥分片。
  3. 后端收集到预设门限数量的分片(M-of-N)后:
    • 在内存中合成主密钥 MK
    • 使用 MK 解密磁盘上的业务密钥密文 EncMK(BKx),得到明文 BKx
    • BKx 管理在一个内存中的安全结构中(仅在进程生命周期内有效)。
    • 立刻从内存中清除 MK 和所有分片内容。
    • 更新状态为 Unlocked。
  4. 之后的加解密请求将直接使用内存中的业务密钥 BKx,不再需要主密钥参与。
  5. 重启 KMS 时,状态重置为 Locked,需要重新收集合伙人分片并解锁。

5.6 分片合成算法

当前阶段采用简单 XOR 拼接方案,后期可升级为 Shamir Secret Sharing。

  • 分片生成(离线工具完成)

    • 假设主密钥 MK 为 32 字节。
    • 若门限 M = 3,则生成 3 个分片:
      • shard_1 = 随机 32 字节
      • shard_2 = 随机 32 字节
      • shard_3 = MK XOR shard_1 XOR shard_2
    • 这 3 个分片分别交给 3 位合伙人保管。
  • 分片合成(KMS 运行时)

    • 收集到全部 M 个分片后:
      • MK = shard_1 XOR shard_2 XOR shard_3
    • 合成完毕后立刻清除所有分片内容。
  • 配置项

    • KMS_SHARD_REQUIRED=3:需要收集的分片数量(门限 M)。
  • 后期升级

    • 若需要"任意 M-of-N"(例如 5 人中任意 3 人即可还原),可改用 Shamir Secret Sharing 算法(Go 库:hashicorp/vault/shamir)。
    • 当前简单 XOR 方案要求必须全部 M 人到齐。

5.7 业务密钥密文文件格式

业务密钥以 JSON 文件形式存储在 /data/kms/keys/ 目录下,文件名为 {key_id}.json

示例文件 /data/kms/keys/cmk-123456.json

{
  "key_id": "cmk-123456",
  "name": "用户数据加密密钥",
  "algorithm": "AES_256_GCM",
  "usage": "DATA_ENCRYPTION",
  "versions": [
    {
      "version": "v1",
      "status": "DISABLED",
      "iv": "BASE64_IV_12_BYTES",
      "ciphertext": "BASE64_ENCRYPTED_KEY_MATERIAL",
      "tag": "BASE64_TAG_16_BYTES",
      "created_at": "2025-01-01T00:00:00Z"
    },
    {
      "version": "v2",
      "status": "ACTIVE",
      "iv": "BASE64_IV_12_BYTES",
      "ciphertext": "BASE64_ENCRYPTED_KEY_MATERIAL",
      "tag": "BASE64_TAG_16_BYTES",
      "created_at": "2025-06-01T00:00:00Z"
    }
  ],
  "current_version": "v2",
  "created_at": "2025-01-01T00:00:00Z",
  "updated_at": "2025-06-01T00:00:00Z"
}

字段说明:

  • key_id:业务密钥唯一标识。
  • algorithm:该密钥使用的算法。
  • versions:密钥版本列表,每个版本包含:
    • iv:初始化向量(AES-GCM 需要 12 字节)。
    • ciphertext:被主密钥加密后的密钥材料。
    • tag:认证标签(AES-GCM 需要 16 字节)。
    • statusACTIVE / DISABLED / DESTROYED
  • current_version:当前用于加密的默认版本。

6. 合伙人分片输入的网页面板

6.1 需求概述

提供一个网页操作面板,仅用于在 KMS 处于 Locked 状态时,让多名合伙人依次输入密钥分片,用于合成主密钥并解锁 KMS。

要求:

  • 网页仅在内网/管理网络访问。
  • 提供当前 KMS 状态与已提交分片数量的可视化提示。
  • 分片内容只在内存中短暂存在,不写入日志或磁盘。
  • 每次提交分片都要记录审计信息(不包含分片明文)。

6.2 前后端交互流程

假设门限为 M-of-N:

  1. 合伙人访问 https://kms-admin.internal/shard
  2. 页面展示:
    • 当前 KMS 状态:Locked / Unlocked。
    • 已提交分片数量:k / M
  3. 每位合伙人在页面上输入:
    • 合伙人标识(可选):如 "partner_id"。
    • 分片内容(字符串或 Base64 字符串)。
  4. 提交后,前端调用 POST /api/admin/shard/submit 接口。
  5. 后端:
    • 校验当前仍为 Locked 状态。
    • 将分片暂存在内存中的 ShardCollector
    • 做去重处理(同一合伙人重复提交时覆盖或拒绝)。
    • 记录审计日志(时间、来源 IP、合伙人标识,不记录分片内容)。
    • 返回当前已提交分片数 k 与门限 M
  6. k == M 时:
    • 后端调用领域层的分片合成逻辑,获得主密钥 MK
    • 调用解锁流程,用 MK 解密业务密钥并加载到内存,清除 MK 与所有分片。
    • 更新 KMS 状态为 Unlocked。
    • 返回前端 unlocked: true,前端页面提示"已解锁"。

6.3 接口设计

6.3.1 提交分片

  • URLPOST /api/admin/shard/submit
  • 请求示例
{
  "partner_id": "partner-1",
  "shard": "BASE64_OR_TEXT"
}
  • 响应示例
{
  "accepted": true,
  "current_count": 2,
  "required": 3,
  "unlocked": false
}

所有管理接口都必须有强认证(如 mTLS + 管理员 Token + IP 白名单)。

6.3.2 查询当前状态

  • URLGET /api/admin/shard/status
  • 响应示例
{
  "status": "LOCKED",
  "current_count": 2,
  "required": 3
}

前端在页面加载时和分片提交后调用该接口,刷新显示当前状态。

6.4 后端内部模块

  • ShardCollector(分片收集器)

    • 负责在 Locked 状态下收集各合伙人分片。
    • 管理分片列表、去重逻辑和计数。
    • 当分片数量达到门限时,触发主密钥合成逻辑。
  • KeyUnlocker(解锁器)

    • 接收合成后的主密钥 MK
    • 解密磁盘上的业务密钥密文 EncMK(BKx) 并加载到内存。
    • 清理 MK 与所有分片。
    • 更新 KMS 状态为 Unlocked。

7. 安全与运维注意事项

  • 认证与授权

    • 数据面接口:通过 client_id + 签名/token、mTLS 等方式认证业务服务身份。
    • 管理面接口:仅限管理网络访问,使用更强的认证(mTLS + 管理员 Token + IP 白名单)。
  • 审计日志

    • 每一次密钥相关操作、解锁操作、分片提交、加解密调用都需要审计记录。
    • 审计记录包含:时间、操作人/Client、KeyId、操作类型、结果等,不包含敏感明文。
    • 禁止记录的内容:plaintext、shard 内容、DEK 明文、主密钥材料。
  • 内存安全

    • 主密钥 MK 和分片只在内存中短暂存在,使用后应覆盖清理。
    • 业务密钥 BKx 在进程内常驻,需结合系统加固策略(限制调试/内存 dump/进程注入等)。
  • 高可用与扩展性

    • KMS 服务以多实例部署,前面通过负载均衡;状态(Locked/Unlocked)和已提交分片计数可以放在集中存储(例如 Redis 或 DB)中统一管理。
    • 密钥元数据、审计日志等持久化在可靠的数据库中。

8. 后续扩展方向

  • 对接硬件 HSM 或云厂商 KMS,将主密钥托管到更高安全等级的组件中。
  • 完善密钥生命周期管理:密钥版本、轮换策略、吊销与销毁流程。
  • 增强搜索与报表:统计各密钥的使用情况、各业务的加解密调用量等。
  • 引入更复杂的策略引擎:按业务、环境(测试/生产)、操作类型进行精细化权限控制。
  • 分片算法升级:从简单 XOR 升级为 Shamir Secret Sharing,支持"任意 M-of-N"门限。
  • 提供多语言客户端 SDK(Go/PHP/Java),封装认证、重试、DEK 缓存等逻辑,降低业务接入成本。

9. 容器化与集成(Docker / Docker Compose)

9.1 将 KMS 打包为 Docker 镜像

  • 配置通过环境变量或配置文件路径注入

    • 示例环境变量:
      • KMS_HTTP_PORT=9455
      • KMS_DB_DSN=mysql://user:pass@tcp(db:3306)/kms
      • KMS_REDIS_ADDR=redis:6379
      • KMS_SHARD_REQUIRED=3
      • KMS_ENV=prod
  • 单命令启动

    • 容器入口命令简单,例如:./kms./kms-server
    • 解锁(分片输入)过程在运行时通过管理接口完成,而不是在容器启动命令中交互式完成。
  • 提供健康检查接口

    • 提供 GET /health 接口,用于容器编排系统(docker-compose、Kubernetes 等)判定服务是否存活。
    • 健康检查仅用于进程/依赖存活判断,不代表 KMS 已处于 Unlocked 状态;是否解锁可通过 /api/admin/shard/status 等管理接口查看。
  • 日志输出到 stdout/stderr

    • 主业务日志输出到标准输出,便于 docker logs 以及后续集中日志系统(如 ELK、Loki 等)收集。
  • 优雅退出

    • 捕获 SIGTERMSIGINT 信号,完成:
      • 停止接收新请求。
      • 等待正在执行的请求完成。
      • 清理内存中的业务密钥(可选)。
  • 持久化数据不打包进镜像

    • 数据库(MySQL/PG)、Redis 等作为单独容器或外部服务。
    • 若磁盘上存储业务密钥密文(EncMK(BKx))文件:
      • 例如目录 /data/kms/keys
      • 通过 volume 挂载到宿主机或数据卷,使其不随镜像更新而丢失。

在此基础上,可用多阶段构建 Dockerfile:构建阶段使用官方 Go 镜像编译,运行阶段使用轻量镜像(如 alpine),将二进制复制进去后以 CMD ["/app/kms"] 形式启动。

9.2 与现有大项目 docker-compose 集成

假设已有一个整体大项目,使用 docker-compose.yml 管理多个服务,KMS 可以作为其中一个普通 service 挂载进去。示例:

services:
  kms:
    image: your-registry/kms:latest
    container_name: kms
    restart: always
    environment:
      - KMS_HTTP_PORT=9455
      - KMS_DB_DSN=mysql://user:pass@tcp(db:3306)/kms
      - KMS_REDIS_ADDR=redis:6379
      - KMS_SHARD_REQUIRED=3
      - KMS_ENV=prod
    ports:
      - "9455:9455"          # 如需对宿主机暴露端口;若只在内部网络访问可省略
    volumes:
      - kms-keys:/data/kms/keys   # 存放 EncMK(BKx) 等业务密钥密文
    depends_on:
      - db
      - redis
    networks:
      - internal_net

  user-service:
    image: your-registry/user-service:latest
    environment:
      - KMS_BASE_URL=http://kms:9455
      - KMS_CLIENT_ID=user-service
      - KMS_CLIENT_SECRET=xxxxx
    depends_on:
      - kms
    networks:
      - internal_net

volumes:
  kms-keys:

networks:
  internal_net:
    driver: bridge

要点说明:

  • 同一网络

    • KMS 与其它需要调用它的服务(如 user-service)置于同一个 docker 网络(例如 internal_net)中,使其能通过 http://kms:9455 访问。
  • 环境变量传递

    • 业务服务通过环境变量获取 KMS 的地址与认证信息,例如:KMS_BASE_URLKMS_CLIENT_IDKMS_CLIENT_SECRET 等。
  • 卷挂载

    • 使用 kms-keys 数据卷挂载 /data/kms/keys,保证容器重启或镜像升级后,业务密钥密文文件不会丢失。
  • 端口暴露策略

    • 若 KMS 仅供内部服务使用,可不在 ports 中暴露端口,仅使用内部网络访问,提高安全性。
    • 管理面(含解锁/分片输入网页)建议仅通过内网访问,必要时可使用单独的管理网络或进一步的访问控制。

9.3 容器化后的解锁流程

容器化后,KMS 的 Locked/Unlocked 状态机和解锁流程本质不变:

  1. docker-compose 启动容器 → KMS 进程随容器启动,初始状态为 Locked
  2. 运维人员/合伙人通过管理网络访问简版网页 /admin/shard 或管理 API:
    • 依次提交主密钥分片。
    • 分片收集到门限数量后,服务在内存中合成主密钥 MK,解密业务密钥密文并加载到内存。
    • 清理主密钥与分片,状态切换为 Unlocked
  3. 内部业务服务通过 KMS_BASE_URL 调用 Encrypt / Decrypt / GenerateDataKey / DecryptDataKey 等接口进行加解密操作。

从整体大项目的角度看,KMS 在 docker-compose 中就是一个普通后端服务:

  • 由编排系统负责拉起和重启。
  • 由环境变量与网络配置决定与其它服务的集成方式。
  • 由管理端接口负责完成密钥注入与解锁流程。

10. API 详细设计与错误码

10.1 通用约定

  • 协议与数据格式

    • 所有接口使用 HTTPS/HTTP,路径前缀区分数据面与管理面,例如:
      • 数据面:/api/kms/...
      • 管理面:/api/admin/...
    • 请求与响应均使用 JSON 格式。
  • 统一响应结构(示例):

{
  "code": 0,
  "message": "OK",
  "data": { }
}
  • 错误码约定(示例,可按需扩展)
    • 0:成功
    • 1001:认证失败(无效 client_id / 凭证)
    • 1002:无权限访问指定密钥或操作
    • 1003:KMS 当前处于 Locked 状态
    • 2001:请求参数错误(缺失/格式不合法)
    • 2002:指定的 Key 不存在或不可用
    • 2003:算法不支持
    • 3001:内部错误(DB/加密模块异常)

10.2 数据面接口

10.2.1 Encrypt

  • URLPOST /api/kms/encrypt
  • 请求字段
    • key_id(string,必填):业务密钥 ID。
    • plaintext(string,必填):Base64 编码的明文数据。
    • algorithm(string,可选):加密算法,默认使用 key 绑定算法,例如 AES_256_GCM
    • aad(string,可选):Base64 编码的附加认证数据(AAD)。
  • 响应字段(data)
    • ciphertext(string):Base64 编码的密文数据。
    • key_version(string):实际使用的密钥版本号。
    • algorithm(string):实际使用的算法。

10.2.2 Decrypt

  • URLPOST /api/kms/decrypt
  • 请求字段
    • key_id(string,必填):业务密钥 ID(或可从密文元数据推断)。
    • ciphertext(string,必填):Base64 编码的密文数据。
    • algorithm(string,可选):加密算法,默认使用 key 绑定算法。
    • aad(string,可选):Base64 编码的 AAD,需与加密时一致。
  • 响应字段(data)
    • plaintext(string):Base64 编码的明文数据。
    • key_version(string):实际使用的密钥版本号。
    • algorithm(string):实际使用的算法。

10.2.3 GenerateDataKey

  • URLPOST /api/kms/generate-data-key
  • 请求字段
    • key_id(string,必填):用于加密 DEK 的 CMK/业务密钥 ID。
    • key_spec(string,必填):DEK 类型,例如 AES_256
  • 响应字段(data)
    • plaintext_data_key(string):Base64 编码的 DEK 明文(仅在安全场景下返回)。
    • ciphertext_data_key(string):Base64 编码的被 CMK 加密的 DEK 密文。
    • key_version(string):用于加密该 DEK 的 CMK 版本号。

10.2.4 DecryptDataKey

  • URLPOST /api/kms/decrypt-data-key
  • 请求字段
    • ciphertext_data_key(string,必填):Base64 编码的 DEK 密文。
  • 响应字段(data)
    • plaintext_data_key(string):Base64 编码的 DEK 明文。
    • key_id(string):对应的 CMK ID。
    • key_version(string):对应的 CMK 版本号。

10.3 管理面接口(部分)

10.3.1 健康检查

  • URLGET /health
  • 响应示例
{
  "status": "UP",
  "db": "OK",
  "redis": "OK"
}

10.3.2 查询 Locked/Unlocked 状态

  • URLGET /api/admin/status
  • 响应字段(data)
    • status(string):LOCKEDUNLOCKED

10.3.3 提交分片

  • URLPOST /api/admin/shard/submit
  • 请求字段
    • partner_id(string,可选):合伙人标识。
    • shard(string,必填):分片内容(可为 Base64 文本)。
  • 响应字段(data)
    • accepted(bool):是否接受该分片。
    • current_count(int):当前已收集的分片数量。
    • required(int):门限 M。
    • unlocked(bool):是否已完成解锁。

10.3.4 查询分片收集状态

  • URLGET /api/admin/shard/status
  • 响应字段(data)
    • status(string):LOCKED / UNLOCKED
    • current_count(int):当前已收集的分片数量。
    • required(int):门限 M。

其他如密钥管理(创建/禁用/轮换)、Client 管理等接口可在后续迭代中补充。

11. 数据模型与表结构草案

以下表结构为初稿,可在实际建表时根据具体数据库(MySQL/PG)语法和索引需求调整。

11.1 业务密钥表 kms_keys

  • id (bigint, PK)
  • key_id (varchar(64), 唯一索引):业务密钥标识,例如 cmk-123456
  • name (varchar(128)):密钥名称/描述。
  • usage (varchar(32)):用途,例如 DATA_ENCRYPTIONDATA_KEY_ENCRYPTION
  • algorithm (varchar(32)):算法,例如 AES_256_GCM
  • current_version (varchar(16)):当前活跃版本号。
  • enabled (tinyint):是否启用,1=是,2=否。
  • created_at (datetime)
  • updated_at (datetime)

11.2 密钥版本表 kms_key_versions

  • id (bigint, PK)
  • key_id (varchar(64), 索引):业务密钥标识,对应 kms_keys.key_id
  • version (varchar(16)):版本号,例如 v1v2
  • iv (varchar(64)):Base64 编码的初始化向量。
  • ciphertext_key (text):Base64 编码的被主密钥加密后的密钥材料。
  • tag (varchar(64)):Base64 编码的认证标签。
  • algorithm (varchar(32)):算法。
  • status (varchar(16)):ACTIVE / DISABLED / DESTROYED
  • created_at (datetime)
  • updated_at (datetime)
  • 联合唯一索引:(key_id, version)

11.3 客户端表 kms_clients

  • id (bigint, PK)
  • client_id (varchar(64), 唯一索引):调用方标识,例如 user-service
  • client_secret (varchar(256)):调用方密钥(建议加密存储)。
  • name (varchar(128)):调用方名称。
  • status (varchar(16)):ACTIVE / DISABLED
  • created_at (datetime)
  • updated_at (datetime)

11.4 策略表 kms_policies

  • id (bigint, PK)
  • client_id (varchar(64), 索引):对应 kms_clients.client_id
  • key_id (varchar(64), 索引):对应 kms_keys.key_id
  • operations (varchar(256)):允许的操作列表,例如 ENCRYPT,DECRYPT,GENERATE_DATA_KEY
  • created_at (datetime)
  • updated_at (datetime)
  • 联合唯一索引:(client_id, key_id)

11.5 审计日志表 kms_audit_logs

  • id (bigint, PK)
  • timestamp (datetime, 索引):操作时间。
  • client_id (varchar(64)):调用方。
  • key_id (varchar(64)):涉及的密钥(若有)。
  • key_version (varchar(16)):涉及的密钥版本(若有)。
  • operation (varchar(32)):操作类型,例如 ENCRYPTDECRYPTGENERATE_DATA_KEYUNLOCKSHARD_SUBMIT 等。
  • result (varchar(16)):SUCCESS / FAILURE
  • error_code (int, 可空):若失败,对应错误码。
  • remote_ip (varchar(64)):调用方 IP。
  • extra (text/json):扩展信息(不包括敏感明文)。

若需要管理分片持久化记录,可增加 kms_shard_events 表,仅记录分片提交事件的元信息(不包含分片内容)。

12. 运行时密钥管理

12.1 内存中的密钥存储结构

解锁后,业务密钥明文常驻内存。Go 中推荐使用以下结构:

// KeyStore 管理内存中的业务密钥
type KeyStore struct {
    mu     sync.RWMutex
    status string                  // "LOCKED" / "UNLOCKED"
    keys   map[string]*KeyEntry    // key_id -> KeyEntry
}

// KeyEntry 单个业务密钥的内存结构
type KeyEntry struct {
    KeyID          string
    Name           string
    Algorithm      string
    CurrentVersion string
    Versions       map[string]*KeyVersion // version -> KeyVersion
}

// KeyVersion 密钥版本
type KeyVersion struct {
    Version   string
    Status    string   // "ACTIVE" / "DISABLED"
    PlainKey  []byte   // 明文密钥材料(仅在内存中)
}
  • 使用 sync.RWMutex 保护并发访问。
  • PlainKey 只在解锁后存在于内存中,进程退出即消失。
  • 若对内存安全有更高要求,可引入 awnumar/memguard 库对敏感字节做保护。

12.2 多实例状态同步方案

当 KMS 以多实例方式部署时,需要解决两个问题:

  • 分片收集状态:多个实例共享已提交分片的进度。
  • 解锁状态:一个实例解锁后,其他实例如何感知并解锁。

推荐方案(简单稳健):

  • 分片收集使用 Redis 集中存储

    • kms:unlock:shards (Hash):存放各合伙人已提交的分片(注意:仅在 Locked 期间短暂存在,解锁后立即删除)。
    • kms:unlock:count (String):当前已收集的分片数量。
    • 任一实例在收到分片后,写入 Redis;任一实例检测到数量达标后,触发解锁。
  • 解锁后的业务密钥不跨实例共享

    • 每个实例独立执行:从磁盘/DB 读取业务密钥密文 → 用合成的主密钥解密 → 加载到本实例内存。
    • 这样即使某实例挂掉重启,只需重新走解锁流程即可。
  • 解锁状态同步

    • 使用 Redis 记录全局状态:kms:status = "LOCKED" / "UNLOCKED"
    • 任一实例完成解锁后,更新 kms:status = "UNLOCKED" 并发布通知(可用 Redis Pub/Sub 或轮询)。
    • 其他实例收到通知后,自行从磁盘/DB 加载业务密钥(此时主密钥已被清除,所以其他实例需要在首次解锁时也参与分片合成,或者采用"一次解锁 + 临时密钥中转"的方式)。

13. 密钥轮换流程

13.1 轮换时机

  • 定期轮换:根据安全策略,每 N 个月/年轮换一次。
  • 事件触发:怀疑密钥泄露、人员变动等。

13.2 轮换步骤

  1. 创建新版本

    • 管理员通过管理接口 POST /api/admin/key/{key_id}/rotate 触发。
    • 后端生成新的密钥材料,用主密钥加密后存入密钥版本表/文件,状态为 ACTIVE
    • 更新 current_version 指向新版本。
  2. 新数据使用新版本

    • 所有新的加密请求自动使用 current_version(新版本)。
    • 响应中返回 key_version,业务可随密文一起存储。
  3. 旧数据解密不受影响

    • 解密时根据密文携带的 key_version 选择对应版本的密钥。
    • 旧版本保持 ACTIVEDISABLED(只允许解密,不允许加密)。
  4. 可选:后台迁移旧数据

    • 若需要将旧数据迁移到新版本加密,可开发一个后台任务:
      • 查询使用旧版本加密的数据。
      • 解密后用新版本重新加密,更新存储。
    • 此步骤可选,视安全需求而定。
  5. 旧版本销毁

    • 当确认旧版本不再被任何数据引用时,可将其状态改为 DESTROYED
    • 销毁后该版本密钥材料从内存和存储中彻底删除,无法恢复。

14. 监控指标

14.1 业务指标(Prometheus metrics)

  • 加解密相关

    • kms_encrypt_total{key_id, status}:Encrypt 请求总数(按 key_id、成功/失败分组)。
    • kms_decrypt_total{key_id, status}:Decrypt 请求总数。
    • kms_generate_datakey_total{key_id, status}:GenerateDataKey 请求总数。
    • kms_decrypt_datakey_total{key_id, status}:DecryptDataKey 请求总数。
    • kms_request_duration_seconds{operation}:各操作的延迟分布(Histogram)。
  • 状态相关

    • kms_status{status}:当前 KMS 状态(Locked=0, Unlocked=1)。
    • kms_shard_collected:当前已收集的分片数量。
    • kms_shard_required:需要的分片数量(门限 M)。
  • 密钥相关

    • kms_keys_total{status}:密钥数量(按启用/禁用分组)。
    • kms_key_versions_total{key_id, status}:各密钥的版本数量。

14.2 系统指标

  • Go 运行时指标(通过 promhttp 默认暴露):

    • go_goroutines:goroutine 数量。
    • go_memstats_*:内存使用。
  • HTTP 服务指标:

    • http_requests_total{method, path, status_code}:HTTP 请求总数。
    • http_request_duration_seconds{method, path}:HTTP 请求延迟。

14.3 告警建议

  • KMS 长时间处于 Locked 状态:服务启动超过 N 分钟仍未解锁。
  • 加解密错误率突增:短时间内失败率超过阈值。
  • 异常 QPS:某个 client_id 或 key_id 的调用量异常(可能被滥用或攻击)。

15. Go 工程启动流程说明

15.1 启动流程概述

  1. 读取配置
    • 从环境变量或配置文件中读取:端口、DB/Redis 连接、日志级别、分片门限等。
  2. 初始化基础设施
    • 初始化 logger。
    • 建立 DB 连接池(MySQL/PG)。
    • 初始化 Redis 连接(用于多实例状态同步)。
    • 初始化 metrics(Prometheus)。
  3. 初始化领域/应用服务
    • 创建 KeyStore(内存密钥存储,初始为空)。
    • 创建 ShardCollector(分片收集器)。
    • 创建各仓储实现(repo),注入 DB/Redis 依赖。
    • 创建领域服务(密钥生命周期、策略校验、解锁器等)。
    • 创建应用层服务(EncryptService、DataKeyService、UnlockService 等)。
  4. 注册路由与中间件
    • 注册数据面接口(/api/kms/...)与管理面接口(/api/admin/...)。
    • 中间件:日志、鉴权、限流、panic 恢复、Locked 状态拦截等。
  5. 启动 HTTP 服务
    • 使用 KMS_HTTP_PORT=9455,监听 :9455
    • 初始状态为 Locked
  6. 信号处理与优雅退出
    • 监听 SIGTERMSIGINT 信号。
    • 收到信号后:
      • 停止接收新请求,等待当前请求处理完成。
      • 清理内存中缓存的业务密钥(覆盖 []byte 后置空)。
      • 关闭 DB/Redis 连接等资源。

16. 实现状态 (2025-12-07 更新)

16.1 已完成功能

功能模块 状态 说明
基础骨架 main.go, 配置加载, 日志, 路由
健康检查 GET /health
状态查询 GET /api/admin/status
分片收集 XOR 分片方案, ShardCollector
分片解锁 POST /api/admin/shard/submit
合伙人初始化 多步骤初始化向导
密码保护 bcrypt 哈希, 三要素验证
SQLite 存储 合伙人信息, 初始化状态
业务密钥管理 创建/列表/禁用/删除 CMK
加密接口 POST /api/kms/encrypt
解密接口 POST /api/kms/decrypt
GenerateDataKey POST /api/kms/generate-data-key
DecryptDataKey POST /api/kms/decrypt-data-key
管理页面 解锁页/初始化/密钥管理/合伙人管理
Docker 支持 Dockerfile

16.2 项目结构

mykms/
├── cmd/kms/main.go                    # 入口
├── internal/
│   ├── api/http/
│   │   ├── router.go                  # 路由配置
│   │   ├── handler.go                 # 解锁处理器
│   │   ├── handler_init.go            # 初始化处理器
│   │   ├── handler_key.go             # 密钥管理处理器
│   │   └── handler_kms.go             # 加解密处理器
│   ├── app/
│   │   ├── admin/
│   │   │   ├── init_service.go        # 初始化服务
│   │   │   ├── key_service.go         # 密钥管理服务
│   │   │   └── unlock_service.go      # 解锁服务
│   │   └── kms/
│   │       └── crypto_service.go      # 加解密服务
│   ├── domain/
│   │   ├── entity/partner.go          # 合伙人实体
│   │   ├── repo/partner_repo.go       # 仓储接口
│   │   └── service/
│   │       ├── keystore.go            # 密钥存储
│   │       └── shard_collector.go     # 分片收集器
│   └── infra/
│       ├── config/config.go           # 配置
│       ├── db/
│       │   ├── sqlite.go              # SQLite 初始化
│       │   └── partner_sqlite_repo.go # 仓储实现
│       └── logger/logger.go           # 日志
├── pkg/response/response.go           # 统一响应
├── tools/gen_shards.go                # 测试工具
├── data/kms/                          # 运行时数据
├── Dockerfile
├── go.mod
└── README.md

16.3 API 接口清单

管理面接口

方法 路径 说明
GET /health 健康检查
GET /api/admin/status KMS 状态
POST /api/admin/shard/submit 提交分片 (含密码验证)
GET /api/admin/init/status 初始化状态
POST /api/admin/init/start 开始初始化
POST /api/admin/init/register 注册合伙人
POST /api/admin/init/complete 完成初始化
POST /api/admin/init/cancel 取消初始化
GET /api/admin/partners 合伙人列表
GET /api/admin/keys 密钥列表
POST /api/admin/keys 创建密钥
POST /api/admin/keys/disable 禁用密钥
POST /api/admin/keys/delete 删除密钥

数据面接口

方法 路径 说明
POST /api/kms/encrypt 加密数据
POST /api/kms/decrypt 解密数据
POST /api/kms/generate-data-key 生成数据密钥
POST /api/kms/decrypt-data-key 解密数据密钥

管理页面

路径 说明
/admin/shard 解锁页面
/admin/init 初始化向导
/admin/keys 密钥管理
/admin/partners 合伙人管理

16.4 配置项

环境变量 默认值 说明
KMS_HTTP_PORT 9455 HTTP 端口
KMS_SHARD_REQUIRED 3 解锁门限
KMS_KEYS_PATH ./data/kms/keys 密钥文件路径
KMS_DB_PATH ./data/kms/kms.db SQLite 路径
KMS_ENV dev 环境

16.5 待完成功能

  • Client 认证与授权 (client_id/client_secret)
  • 审计日志持久化
  • 密钥轮换
  • Prometheus 监控指标
  • 多实例状态同步
  • 实体 U 盘支持

About

自建 KMS(密钥管理服务)

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors