You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于AWS KMS的列级独立密钥客户端加密及密钥轮换最佳方案

基于TypeORM+RDS PostgreSQL的AWS KMS客户端加密实践方案

一、单密钥vs列级密钥:决策逻辑

  • 列级密钥优先:如果不同列数据敏感度差异大(比如手机号、银行卡号 vs 普通用户昵称),列级密钥能把泄露影响范围降到最小——某列密钥泄露只会影响该列数据,不会波及全表,同时符合最小权限原则,审计追踪也更清晰。
  • 单密钥仅适合低敏感度场景:如果所有加密列的敏感度一致,且管理成本优先级高于极致安全,单密钥会更省心,但一旦密钥泄露,所有加密数据都面临风险。
    结合你的需求(追求高安全性),推荐列级独立密钥方案。

二、动态创建列级密钥的落地实现(TypeORM适配)

利用TypeORM的元数据、自定义装饰器和迁移机制,可以自动处理新增/修改encrypt:true列时的密钥创建:

1. 自定义加密列装饰器

封装一个@EncryptedColumn装饰器替代原生@Column,标记需要加密的列并存储关联元数据:

import { Column, ColumnOptions } from "typeorm";

export function EncryptedColumn(options: ColumnOptions & { encrypt: true }) {
  return function (target: any, propertyKey: string) {
    Column(options)(target, propertyKey);
    // 在实体元数据中标记加密列,后续用于扫描
    Reflect.defineMetadata(`encrypted:${propertyKey}`, true, target.constructor);
  };
}

2. 应用启动时自动扫描创建密钥

在TypeORM连接建立后,扫描所有实体元数据,找出加密列并检查是否已有对应KMS密钥,未创建则调用AWS KMS API生成:

import { getConnection } from "typeorm";
import { KMSClient, CreateKeyCommand } from "@aws-sdk/client-kms";

const kmsClient = new KMSClient({ region: "你的AWS区域" });

async function initEncryptedColumnsKeys() {
  const connection = getConnection();
  const entities = connection.entityMetadatas;

  for (const entity of entities) {
    for (const column of entity.columns) {
      const isEncrypted = Reflect.getMetadata(`encrypted:${column.propertyName}`, entity.target);
      if (isEncrypted) {
        // 查询密钥配置表(需提前创建,存储列与KMS密钥的映射)
        const existingKey = await connection.getRepository(ColumnEncryptionKey).findOne({
          where: { entityName: entity.name, columnName: column.propertyName }
        });
        if (!existingKey) {
          const createKeyCmd = new CreateKeyCommand({
            Description: `Encryption key for ${entity.name}.${column.propertyName}`,
            KeyUsage: "ENCRYPT_DECRYPT",
            Origin: "AWS_KMS"
          });
          const keyResult = await kmsClient.send(createKeyCmd);
          // 保存密钥ID到配置表
          await connection.getRepository(ColumnEncryptionKey).save({
            entityName: entity.name,
            columnName: column.propertyName,
            kmsKeyId: keyResult.KeyMetadata!.KeyId!
          });
        }
      }
    }
  }
}

// 在TypeORM连接初始化完成后调用
initEncryptedColumnsKeys();

3. 迁移脚本中自动处理密钥创建

用TypeORM迁移工具管理表结构时,可在迁移脚本中注入密钥创建逻辑:

import { MigrationInterface, QueryRunner } from "typeorm";
import { KMSClient, CreateKeyCommand } from "@aws-sdk/client-kms";

const kmsClient = new KMSClient({ region: "你的AWS区域" });

export class AddEncryptedPhoneColumn1699999999999 implements MigrationInterface {
  public async up(queryRunner: QueryRunner): Promise<void> {
    // 创建加密列
    await queryRunner.query(`ALTER TABLE "user" ADD "phone" varchar NOT NULL`);
    // 创建对应KMS密钥
    const keyResult = await kmsClient.send(new CreateKeyCommand({
      Description: "Encryption key for user.phone",
      KeyUsage: "ENCRYPT_DECRYPT"
    }));
    // 存入密钥配置表
    await queryRunner.query(`INSERT INTO "column_encryption_keys" ("entityName", "columnName", "kmsKeyId") VALUES ('user', 'phone', $1)`, [keyResult.KeyMetadata!.KeyId!]);
  }

  public async down(queryRunner: QueryRunner): Promise<void> {
    // 先删除密钥配置记录(建议归档而非删除KMS密钥,避免历史数据无法解密)
    await queryRunner.query(`DELETE FROM "column_encryption_keys" WHERE "entityName" = 'user' AND "columnName" = 'phone'`);
    await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "phone"`);
  }
}

三、密钥轮换最佳实践(结合AWS KMS)

1. 优先使用AWS KMS自动轮换

AWS KMS支持对CMK开启自动密钥轮换(默认每年一次),轮换后KMS会自动保留旧密钥版本,解密历史数据时会自动匹配对应版本,无需手动处理。开启方式:在KMS控制台找到对应密钥,启用"自动密钥轮换"即可。

2. 列级密钥轮换的核心注意事项

  • 分批重加密:若列数据量较大,不要一次性全量重加密,可按主键分段(比如每次处理1000条)异步执行,避免数据库性能雪崩。
  • 保留旧密钥:轮换后不要立即删除旧密钥,至少保留到所有历史数据完成重加密,且确认无旧数据依赖。
  • 跟踪密钥版本:在密钥配置表中新增keyVersion字段,记录列当前使用的密钥版本,解密时优先用当前版本,失败则尝试旧版本。

3. 数据重加密的TypeORM实现示例

async function reEncryptColumn(entityName: string, columnName: string) {
  const connection = getConnection();
  const keyRepo = connection.getRepository(ColumnEncryptionKey);
  const currentKey = await keyRepo.findOne({ where: { entityName, columnName } });
  if (!currentKey) return;

  // 创建新密钥
  const newKeyResult = await kmsClient.send(new CreateKeyCommand({
    Description: `New encryption key for ${entityName}.${columnName}`,
    KeyUsage: "ENCRYPT_DECRYPT"
  }));
  const newKeyId = newKeyResult.KeyMetadata!.KeyId!;

  // 分批处理数据
  let skip = 0;
  const take = 1000;
  while (true) {
    const entities = await connection.getRepository(entityName).find({ skip, take });
    if (entities.length === 0) break;

    for (const entity of entities) {
      // 用旧密钥解密
      const decryptedData = await decryptWithKMS(currentKey.kmsKeyId, entity[columnName]);
      // 用新密钥加密
      const encryptedData = await encryptWithKMS(newKeyId, decryptedData);
      entity[columnName] = encryptedData;
    }
    await connection.getRepository(entityName).save(entities);
    skip += take;
  }

  // 更新密钥配置为新密钥
  await keyRepo.update({ entityName, columnName }, { kmsKeyId: newKeyId });
}

// 封装KMS信封加密/解密逻辑(避免直接用CMK加密业务数据,降低调用成本)
async function encryptWithKMS(keyId: string, data: string) {
  const { Plaintext, CiphertextBlob } = await kmsClient.send(new GenerateDataKeyCommand({
    KeyId: keyId,
    KeySpec: "AES_256"
  }));
  // 用DEK加密业务数据(此处示例用AES-256-CBC,可按需替换)
  const encryptedData = aesEncrypt(data, Plaintext!);
  // 存储加密数据+加密后的DEK(用分隔符区分)
  return `${encryptedData}:${Buffer.from(CiphertextBlob).toString('base64')}`;
}

async function decryptWithKMS(keyId: string, encryptedDataWithDek: string) {
  const [encryptedData, encryptedDekBase64] = encryptedDataWithDek.split(':');
  const encryptedDek = Buffer.from(encryptedDekBase64, 'base64');
  // 解密DEK
  const { Plaintext } = await kmsClient.send(new DecryptCommand({
    CiphertextBlob: encryptedDek
  }));
  // 解密业务数据
  return aesDecrypt(encryptedData, Plaintext!);
}

四、额外优化建议

  • TypeORM加密器集成:实现TypeORM的CustomEncryption接口,将上述加密解密逻辑封装进去,直接在实体列上配置使用,无需手动调用。
  • IAM权限最小化:给应用服务器的IAM角色配置仅允许GenerateDataKey、Decrypt、CreateKey(若需动态创建)等必要KMS操作的权限。

内容的提问来源于stack exchange,提问作者YouSuggestMe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 18:53:26