工业网关承上启下:对下连接 PLC、逆变器、传感器和历史数据单元,对上连接云平台、运维系统和数据分析服务。数据一旦离开现场,就可能在设备、网关、代理、消息队列、云端数据库、备份和运维终端之间流转。只做一层的加密,往往只能保护链路中的一小段。
本文按工程落地顺序梳理工业网关的数据加密体系:先划清保护边界,再依次落地传输加密、静态加密、字段加密、端到端加密、密钥管理、密钥轮换和运行监测,最后给出常见坑与演进建议。
一、为什么需要全链路加密
工业边缘的数据安全风险不只在"公网被窃听"这一条路径上。更常见的问题是:链路上某一段用了 TLS,但后面的消息队列、本地数据库、日志、备份或运维导出又变成明文。
| 风险点 | 典型后果 |
|---|---|
| 传输窃听 | 设备凭据、控制指令、工艺参数被截获 |
| 中间系统落盘 | 代理、消息队列、日志系统保存明文副本 |
| 网关被盗或替换 | 本地数据库、配置文件、脚本中的敏感数据泄漏 |
| 备份未加密 | 异地备份、U 盘导出、测试环境复用造成二次泄漏 |
| 密钥硬编码 | 代码仓库、镜像或固件泄漏后,加密层整体失效 |
| 缺少密钥轮换 | 一把密钥长期使用,历史数据风险无法收敛 |
全链路加密的目标不是"所有数据都用同一种方式加密",而是明确每类数据的威胁模型,再组合不同层次的控制措施。
二、先划清加密边界
推荐先把数据流拆开,画出从采集点到最终使用方的完整路径,再逐段回答下面几个问题:
| 数据类型 | 需要传输加密 | 需要静态加密 | 需要字段加密 | 说明 |
|---|---|---|---|---|
| 设备遥测数据 | 是 | 按敏感度选择 | 少量关键字段 | 高频数据要权衡性能与合规 |
| 控制指令 | 是 | 是 | 建议 | 还需要完整性校验与审计 |
| 设备身份与凭据 | 是 | 是 | 是 | 优先使用证书或托管凭据 |
| 运维日志 | 是 | 是 | 敏感字段脱敏或加密 | 日志最容易产生意外泄漏 |
| 配置文件 | 是 | 是 | 是 | 不要把解密密钥写在同一份配置里 |
| 数据库备份 | 是 | 是 | 继承字段加密 | 备份和主库要同等对待 |
四类常见加密层各自解决不同问题:
- 传输加密:保护两个端点之间链路,防止窃听和篡改,例如 TLS、mTLS。
- 静态加密:保护磁盘、分区、数据库文件、备份介质,例如 LUKS、加密卷。
- 字段加密:只保护数据库或消息里的敏感字段,常用于多租户、云端共享存储和明文日志治理。
- 端到端加密:由业务发送方和接收方处理密钥,保护 payload 经过代理、消息队列后的隐私。
传输加密不能替代静态加密;静态加密也不能防止应用层把敏感字段写进日志。加密层次要和数据边界一起设计。
三、传输加密:TLS 与 mTLS
1. TLS 的最低要求
工程上建议按下面的基线配置:
- 优先协商 TLS 1.3,最低允许 TLS 1.2;
- 证书私钥保护在文件权限、密钥管理系统或系统凭据存储中;
- 证书域名必须与实际访问的 SNI 或服务名一致;
- 客户端必须校验 CA 和主机名,不能关闭证书校验;
- 云边通道和服务间通道建议使用 mTLS,让网关和服务都持有证书;
- 证书续期、替换和吊销要有流程与演练。
2. aiohttp 客户端 mTLS 示例
客户端要验证服务端证书,所以应使用 Purpose.SERVER_AUTH;如果同时要求客户端证书,再加载客户端证书链。
python
import ssl
import aiohttp
def build_mtls_client_context() -> ssl.SSLContext:
# 客户端用途:验证要访问的服务端
ctx = ssl.create_default_context(
purpose=ssl.Purpose.SERVER_AUTH,
cafile="ca.pem",
)
ctx.minimum_version = ssl.TLSVersion.TLSv1_2
# mTLS:提交客户端身份
ctx.load_cert_chain(
certfile="gateway-client-cert.pem",
keyfile="gateway-client-key.pem",
)
return ctx
async def fetch_device_profile() -> dict:
ctx = build_mtls_client_context()
connector = aiohttp.TCPConnector(ssl=ctx)
async with aiohttp.ClientSession(connector=connector) as session:
async with session.get("https://api.edge.example/devices/dev-001") as resp:
resp.raise_for_status()
return await resp.json()
如果只是普通 TLS,去掉 load_cert_chain() 即可;不要把服务端 CA、客户端证书和私钥混在同一个文件里,除非部署流程明确要求并做好了权限隔离。
3. 服务端 TLS 示例
python
import ssl
def build_server_context() -> ssl.SSLContext:
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.minimum_version = ssl.TLSVersion.TLSv1_2
ctx.load_cert_chain(
certfile="gateway-server-cert.pem",
keyfile="gateway-server-key.pem",
)
# 要求客户端出示由内部 CA 签发的证书
ctx.load_verify_locations(cafile="ca.pem")
ctx.verify_mode = ssl.CERT_REQUIRED
return ctx
生产部署时,通常把证书配置交给 Nginx、Envoy、Kubernetes Ingress 或服务框架处理。无论放在哪一层,都要保证:证书文件可读范围最小、私钥不进代码仓库、证书链完整、监控剩余有效期。
4. gRPC mTLS 示例
python
import grpc
def build_secure_channel(endpoint: str) -> grpc.aio.Channel:
with open("ca.pem", "rb") as f:
root_certificates = f.read()
with open("gateway-client-key.pem", "rb") as f:
private_key = f.read()
with open("gateway-client-cert.pem", "rb") as f:
certificate_chain = f.read()
credentials = grpc.ssl_channel_credentials(
root_certificates=root_certificates,
private_key=private_key,
certificate_chain=certificate_chain,
)
return grpc.aio.secure_channel(endpoint, credentials)
grpc.ssl_target_name_override 只建议在测试环境中模拟名称;生产环境应给服务签发正确 SAN,而不是长期关闭或伪造主机名校验。
四、静态加密:先保护落盘介质
1. 使用 LUKS2 加密数据分区
以下命令会擦除目标分区数据,只能在确认设备路径正确且数据已备份后执行。
bash
# 先确认分区路径,避免误操作系统盘
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS
# 创建 LUKS2 加密分区
sudo cryptsetup luksFormat --type luks2 /dev/sdb1
# 打开为 /dev/mapper/edge-data
sudo cryptsetup open /dev/sdb1 edge-data
# 首次使用前创建文件系统
sudo mkfs.ext4 /dev/mapper/edge-data
# 挂载到工业网关的数据目录
sudo mkdir -p /var/lib/edge
sudo mount /dev/mapper/edge-data /var/lib/edge
# 停止服务后卸载并关闭
sudo umount /var/lib/edge
sudo cryptsetup close edge-data
生产环境还要补齐三件事:
- 开机自动解锁策略:
/etc/crypttab、TPM、远程解锁或受控 keyfile; - keyfile 权限:keyfile 不能与加密数据放在同一块盘;
- 故障恢复演练:确认断电、重启、换盘和恢复流程可执行。
2. PostgreSQL 的正确认知
社区版 PostgreSQL 没有原生的 CREATE TABLESPACE ... WITH (encryption = on) 语法。这个写法在一些商业发行版或第三方方案中可能存在,但不能当作 PostgreSQL 通用能力。
社区版更常见的组合是:
| 目标 | 做法 |
|---|---|
| 数据文件静态加密 | 将 PGDATA 或表空间放在 LUKS2 加密卷上 |
| 传输加密 | 配置 PostgreSQL 的 SSL 连接 |
| 字段级加密 | 应用侧加密,或使用 pgcrypto |
| 备份加密 | 加密备份卷、对象存储服务端加密或客户端加密 |
sql
-- 表空间本身只指定位置;加密由底层加密卷提供
CREATE TABLESPACE edge_data
LOCATION '/var/lib/postgresql/encrypted/edge_data';
如果使用云数据库或商业版 TDE,应以该产品文档为准,明确覆盖范围:数据文件、WAL、临时文件、逻辑备份、复制链路和快照是否都被加密。
五、字段加密:保护最小敏感单元
字段加密适合这些场景:
- 数据库或对象存储由第三方平台托管;
- 同一张表包含普通遥测数据和客户敏感信息;
- 日志、消息副本、索引和备份容易扩散;
- 需要按租户或设备维度使用不同密钥。
但字段加密也有代价:模糊查询、排序、聚合和外键关联会变复杂。因此,不要把所有字段无差别加密,而应先做数据分类。
1. 可检索敏感字段:盲索引
如果需要按手机号、设备序列号或唯一凭据查找,不能直接用随机 IV 加密后存储,因为同一明文每次密文都不同。常见做法是另建一个 HMAC 盲索引。
python
import base64
import hmac
from hashlib import sha256
def blind_index(value: str, index_key: bytes, tenant_id: str) -> str:
# AAD 绑定租户,避免不同租户相同值得到相同索引
mac = hmac.new(index_key, digestmod=sha256)
mac.update(tenant_id.encode("utf-8"))
mac.update(b"\x00")
mac.update(value.encode("utf-8"))
return base64.urlsafe_b64encode(mac.digest()).decode("ascii").rstrip("=")
注意:加密密钥和盲索引密钥必须分开。盲索引只适合等值查询,不解决范围查询。
2. 存储结构
sql
CREATE TABLE edge_device_secret (
device_id TEXT PRIMARY KEY,
secret_ciphertext BYTEA NOT NULL,
key_version INT NOT NULL,
algorithm TEXT NOT NULL DEFAULT 'AES-256-GCM',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
数据库中保存密文、算法和密钥版本,不保存明文密钥。algorithm 和 key_version 是后续算法演进与密钥轮换的基础。
六、AES-GCM:认证加密的推荐写法
对应用层字段加密,优先使用 AES-256-GCM 这类认证加密算法。它同时提供机密性和完整性校验,能发现密文被篡改。
工程规则:
- AES-256 密钥长度为 32 字节;
- 每次加密必须生成新的随机 nonce;
- 尽量使用密码库的高层 API,减少手写加密流程出错;
- 用 AAD 绑定业务上下文,例如租户 ID、字段名、记录版本;
- 密文结构中保存算法、密钥版本、nonce 和认证标签。
python
import base64
import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
def new_data_key() -> bytes:
return AESGCM.generate_key(bit_length=256)
def encrypt_field(
key: bytes,
plaintext: str,
tenant_id: str,
field_name: str,
) -> dict:
if len(key) != 32:
raise ValueError("AES-256-GCM key must be 32 bytes")
nonce = os.urandom(12)
aad = f"{tenant_id}|{field_name}".encode("utf-8")
ciphertext = AESGCM(key).encrypt(
nonce=nonce,
data=plaintext.encode("utf-8"),
associated_data=aad,
)
return {
"alg": "AES-256-GCM",
"nonce": base64.b64encode(nonce).decode("ascii"),
"ciphertext": base64.b64encode(ciphertext).decode("ascii"),
"aad": f"{tenant_id}|{field_name}",
}
def decrypt_field(
key: bytes,
envelope: dict,
) -> str:
if envelope.get("alg") != "AES-256-GCM":
raise ValueError("unsupported algorithm")
nonce = base64.b64decode(envelope["nonce"])
ciphertext = base64.b64decode(envelope["ciphertext"])
aad = envelope["aad"].encode("utf-8")
plaintext = AESGCM(key).decrypt(
nonce=nonce,
data=ciphertext,
associated_data=aad,
)
return plaintext.decode("utf-8")
七、端到端加密:别让中间系统变成新的明文点
传输加密保护的是"当前这一跳"。如果数据路径是:
text
现场设备 -> 工业网关 -> 消息代理 -> 云端服务 -> 数据仓库
每一段都使用 TLS,仍然不代表消息代理、云端服务或数据仓库看不到 payload。只要其中任何系统把消息体落盘成业务日志,敏感数据就可能扩散。
端到端加密是在应用层由发送方和最终接收方处理密钥:
- 发送方加密业务 payload;
- 中间系统只转发密文和必要路由元数据;
- 最终接收方解密;
控制指令还可以同时签名,保证来源可验证。
端到端加密的代价是密钥分发与找回。工程上应先回答:
| 问题 | 建议做法 |
|---|---|
| 谁能解密 | 按接收方、租户或数据主题分 key,不做全局共享 key |
| 密钥如何分发 | 使用设备证书、平台公钥体系或密钥管理服务 |
| 元数据是否敏感 | 路由 Topic、设备 ID、时间戳也可能需要脱敏 |
| 中间层还要处理数据吗 | 若需要解析,就不能做完全端到端,只能做字段级保护 |
| 密钥丢失怎么办 | 使用托管密钥、密钥恢复流程或接受不可恢复策略 |
如果云端服务必须读取完整 payload,就不能宣称端到端加密;更准确的描述是"传输加密 + 受控服务端解密 + 字段级或静态加密"。
八、信封加密与密钥轮换
直接用一把密钥加密所有业务数据,会让轮换变得非常困难。更常见的模型是信封加密:
text
明文数据
|
| AES-256-GCM,使用随机 DEK 加密
v
业务密文
|
| DEK 由当前 KEK 加密保护
v
wrapped_dek + 业务密文 + key_version + nonce + AAD
这样做的好处是:
- DEK 每条记录或每批数据独立生成;
- 轮换 KEK 时只需要重新包装 DEK,不必重加密全部业务数据;
- 云端或数据库只保存 wrapped DEK,无法单独解出数据;
- 支持
key_version,可以平滑迁移算法和密钥。
python
import base64
import os
from dataclasses import dataclass
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
@dataclass(frozen=True)
class KeyEncryptionKey:
key_id: str
key: bytes
def b64encode(data: bytes) -> str:
return base64.b64encode(data).decode("ascii")
def b64decode(data: str) -> bytes:
return base64.b64decode(data.encode("ascii"))
def envelope_encrypt(
plaintext: bytes,
kek: KeyEncryptionKey,
context: str,
) -> dict:
if len(kek.key) != 32:
raise ValueError("KEK must be 32 bytes")
dek = AESGCM.generate_key(bit_length=256)
data_nonce = os.urandom(12)
wrap_nonce = os.urandom(12)
aad = f"v1|{kek.key_id}|{context}".encode("utf-8")
ciphertext = AESGCM(dek).encrypt(
data_nonce, plaintext, f"data|{context}".encode("utf-8")
)
wrapped_dek = AESGCM(kek.key).encrypt(wrap_nonce, dek, aad)
return {
"version": 1,
"alg": "AES-256-GCM",
"kek_id": kek.key_id,
"context": context,
"data_nonce": b64encode(data_nonce),
"wrap_nonce": b64encode(wrap_nonce),
"ciphertext": b64encode(ciphertext),
"wrapped_dek": b64encode(wrapped_dek),
}
def envelope_decrypt(envelope: dict, kek: KeyEncryptionKey) -> bytes:
if envelope.get("version") != 1:
raise ValueError("unsupported envelope version")
if envelope.get("alg") != "AES-256-GCM":
raise ValueError("unsupported algorithm")
if envelope.get("kek_id") != kek.key_id:
raise ValueError("KEK version mismatch")
context = envelope["context"]
aad = f"v1|{kek.key_id}|{context}".encode("utf-8")
dek = AESGCM(kek.key).decrypt(
b64decode(envelope["wrap_nonce"]),
b64decode(envelope["wrapped_dek"]),
aad,
)
return AESGCM(dek).decrypt(
b64decode(envelope["data_nonce"]),
b64decode(envelope["ciphertext"]),
f"data|{context}".encode("utf-8"),
)
密钥轮换建议采用双阶段:
- 切换写入:新生成 KEK v2,新数据使用 v2 包装,v1 保留用于解密;
- 重包装历史数据:后台解出旧 DEK,再用 v2 包装,验证后更新数据库记录;
- 确认无 v1 依赖后停用 v1:删除运行时访问能力,按合规要求归档或销毁。
九、密钥管理:不要让加密体系毁在一把明文 key
密钥管理的重点是生命周期:
| 阶段 | 关键要求 |
|---|---|
| 生成 | 使用密码学安全随机源,密钥长度符合算法要求 |
| 导入 | 通过受控流程或 HSM,不经过聊天工具、邮箱和普通工单 |
| 存储 | Vault、KMS、TPM、系统凭据存储或受控 keyfile |
| 使用 | 最小权限、审计访问、区分 KEK / DEK / 签名密钥 |
| 轮换 | 支持多版本并存,新写入切换,旧数据后台重包装 |
| 吊销 | 能撤销服务或设备访问权限,并评估已落盘密文影响 |
| 备份 | 密钥与数据分开备份,恢复流程必须演练 |
Vault KV 读取示例
python
import base64
import os
import hvac
def load_key_from_vault(key_version: int) -> bytes:
client = hvac.Client(
url=os.environ["VAULT_ADDR"],
token=os.environ["VAULT_TOKEN"],
)
if not client.is_authenticated():
raise RuntimeError("Vault authentication failed")
result = client.secrets.kv.v2.read_secret_version(
mount_point="edge-kv",
path="keys/field-encryption",
version=key_version,
)
encoded = result["data"]["data"][f"kek_v{key_version}"]
return base64.b64decode(encoded)
生产环境不建议把长期 Vault Token 写进环境配置。网关可以使用 AppRole、Kubernetes ServiceAccount、Workload Identity 或云平台托管身份完成认证;边缘离线场景则需要设计本地受保护的密钥缓存和恢复流程。
十、备份、日志与运维导出同样要加密
很多泄漏不是发生在主链路,而是发生在"复制出来的那份"。
| 场景 | 常见问题 | 建议 |
|---|---|---|
| 数据库备份 | 主库加密但备份明文 | 备份文件加密,密钥独立存放 |
| 日志系统 | 请求体被完整打印 | 脱敏、截断、禁止记录明文密文密钥 |
| 故障包 | 含配置、证书、连接串 | 打包前扫描敏感字段,传输后限时销毁 |
| 测试环境 | 生产数据明文复制 | 使用脱敏数据或保留同等加密控制 |
| 移动介质 | U 盘导出无控制 | 禁用或强制加密盘与审批流程 |
| 快照 | 云盘快照继承能力不明确 | 明确快照、异地复制和镜像的加密策略 |
备份恢复要验证两层内容:数据能恢复,密钥也能在受控流程下恢复。只备份数据不备份密钥版本关系,故障时会造成数据不可恢复。
十一、完整性与不可否认:加密之外还要签名
加密解决"别人看不懂",签名解决"来源可信、内容没被改"。对工业网关的控制指令、配置包、固件包和审计事件,建议同时设计签名。
python
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
Ed25519PrivateKey,
Ed25519PublicKey,
)
def sign_command(private_key: Ed25519PrivateKey, command: bytes) -> bytes:
return private_key.sign(command)
def verify_command(
public_key: Ed25519PublicKey,
command: bytes,
signature: bytes,
) -> None:
# 验证失败会抛出 InvalidSignature
public_key.verify(signature, command)
签名前应把易变字段和被签内容完整固化,例如:
json
{
"device_id": "gateway-001",
"command": "set_power_limit",
"value": 80,
"issued_at": "2026-10-19T10:00:00+08:00",
"expires_at": "2026-10-19T10:05:00+08:00",
"nonce": "8f4d0e58-2b17-4c52-9d02-c11c52e3f7ea"
}
签名密钥、TLS 私钥、字段加密 KEK 不要混用。一把密钥一个用途,是密码工程里的基本原则。
十二、加密体系的监测指标
加密链路一旦失效,通常不会在业务功能上立刻暴露,必须靠监测发现。
| 指标 | 说明 |
|---|---|
tls_handshake_total{result} |
握手成功、证书失败、协议失败 |
certificate_expiry_timestamp_seconds |
证书剩余有效期 |
crypto_operation_total{op,result,alg,key_version} |
加密、解密、签名、验签结果 |
crypto_operation_duration_seconds |
密码操作耗时 |
encrypted_volume_active |
LUKS 卷是否处于打开状态 |
key_lookup_total{source,result} |
Vault / KMS / 本地缓存读取结果 |
plaintext_sensitive_field_total |
敏感字段明文扫描命中数 |
backup_encrypted_ratio |
加密备份占比 |
告警建议:
- TLS 握手失败率突然升高;
- 证书剩余有效期低于 30 天;
- 解密失败集中在某个 key version;
- Vault 或 KMS 连续失败;
- LUKS 卷意外关闭或解锁失败;
- 敏感字段扫描发现新增明文路径。
十三、性能与可用性设计
加密会带来 CPU、延迟和故障面变化,但它通常不是性能瓶颈的挡箭牌。更常见的问题是密钥读取设计不合理,例如每条消息都远程访问 KMS。
工程建议:
- TLS 连接复用长连接,避免频繁握手;
- 字段加密只覆盖敏感字段,不做全字段无差别加密;
- 批量数据可按批次复用一个 DEK,而不是每条都生成并包装 DEK;
- KMS 结果使用受控内存缓存,并设置刷新和失败降级策略;
- 关注设备 CPU 是否支持 AES-NI 或硬件加速;
- 解密失败要有明确错误分类,避免把密钥问题误判成业务故障;
- 离线场景必须提前设计本地密钥缓存、开机解锁和恢复流程。
十四、工程实践清单
| 实践 | 落地要点 |
|---|---|
| 全链路数据分类 | 明确遥测、指令、凭据、日志、配置、备份的保护等级 |
| 默认传输加密 | 云边和服务间使用 TLS,重要链路 mTLS |
| 静态加密 | 系统盘、数据卷、数据库目录、备份统一评估 |
| 字段加密 | 只加密敏感字段,配合盲索引和密钥版本 |
| 端到端保护 | 中间系统只转发密文时才可称为端到端 |
| 密钥分层 | KEK / DEK / 签名密钥分离,用途单一 |
| 可轮换 | 多版本并存,新写入切换,历史数据后台重包装 |
| 可恢复 | 密钥、证书、备份的恢复流程都要演练 |
| 可观测 | 握手、证书、加解密、解锁、密钥读取全部有指标 |
| 可审计 | 谁访问过密钥、谁解密过数据、谁下发过指令可追踪 |
十五、常见坑与应对
| 坑 | 影响 | 应对 |
|---|---|---|
| 密钥硬编码在代码或镜像 | 仓库泄漏后加密失效 | 使用 KMS、Vault、TPM 或系统凭据存储 |
| 关闭 TLS 证书校验 | TLS 只剩"加密形式",无法防中间人 | 必须校验 CA 与主机名 |
| 证书域名不匹配 | 上线或续期后握手失败 | 签发正确 SAN,并做证书监控 |
| 把 LUKS keyfile 放在加密盘 | 系统重启后无法解锁 | keyfile 独立保存并限制权限 |
| 误以为 PostgreSQL 社区版有表空间 TDE | 方案评审或上线失败 | 使用 LUKS 加密卷、pgcrypto 或商业方案 |
| AES-GCM nonce 重复 | 认证加密安全性被破坏 | 每次加密使用密码库生成随机 nonce |
| 直接用一把 KEK 加密所有数据 | 轮换代价极高 | 采用信封加密与 DEK |
| 只备份数据不备份密钥版本 | 灾难恢复时无法解密 | 数据与密钥恢复流程同步演练 |
| 日志打印明文 payload | 静态加密被应用层绕开 | 结构化脱敏与敏感字段扫描 |
| 版本迁移无灰度 | 老客户端无法握手或解密 | 多算法、多 key version 并存 |
十六、运行时层面的角色
工业网关运行时需要把加密能力产品化,而不是让每个业务模块各写一套:统一证书加载、mTLS 通道、字段加密 SDK、密钥版本路由、解密审计、失败指标和离线策略,才能避免"有加密代码,但没有加密体系"。
如果在构建工业网关运行时,可以参考 Zenova EdgeOS 在传输加密、静态数据保护和密钥生命周期管理方面的工程实践。
十七、TL;DR
- 全链路加密 = 传输加密 + 静态加密 + 字段加密 + 端到端保护 + 密钥管理。
- 云边和服务间优先 TLS 1.3,最低 TLS 1.2;重要链路使用 mTLS。
- 静态数据用 LUKS2 或平台加密卷保护;社区版 PostgreSQL 没有通用表空间 TDE 语法。
- 字段加密优先 AES-256-GCM,nonce 每次随机,AAD 绑定上下文。
- 密钥采用 KEK + DEK 信封模型,轮换时重包装 DEK,不必重加密全部业务数据。
- 端到端加密要求中间系统不能解密 payload。
- 签名用于完整性与来源验证,不能与加密密钥混用。
- 备份、日志、故障包、测试环境是更常见的泄漏点。
十八、下一步建议
- 完成数据分类与数据流图;
- 落地 TLS / mTLS 与证书监控;
- 加密数据卷、数据库目录和备份;
- 为敏感字段实现 AES-GCM 与信封加密;
- 建立密钥轮换、恢复和审计演练。