题干与适用场景
多租户配置服务要加密数据库中的敏感值。每个租户有独立逻辑密钥,平台需要轮换根密钥、继续读取旧密文,并能检测密文被替换或跨租户复制。请使用 Go 1.26 crypto/hpke 设计信封格式、加密/解密流程、密钥版本、上下文绑定和迁移策略。核心考察密码学 API 的正确组合与生命周期设计,因此归为 coding。
面试官考察点
第一,能否理解 HPKE 的 KEM、KDF、AEAD 分工,并区分公钥封装与对称数据加密。
第二,能否选择 Base、PSK 或 Auth 模式,说明认证密钥和机密性密钥的责任边界。
第三,能否把租户、用途、算法套件和版本绑定到 info 或 AAD,防止跨上下文解密。
第四,能否设计密钥轮换、旧版本读取、撤销和密文重加密,不把版本号当作秘密。
第五,能否用篡改、重放、随机数、失败信息和互操作测试验证实现。
回答前需要澄清的问题
- 配置服务只需机密性,还是还需要发送方认证?
- 接收者公钥由谁托管,是否有 KMS/HSM 和审计要求?
- 轮换后旧密文保留多久,是否需要后台重加密?
- 租户上下文和配置键是否属于可公开的关联数据?
- 是否需要跨语言解密或离线恢复?
- 失败时要区分密钥不存在、版本不支持和认证失败吗?
30 秒回答框架
“我先固定 RFC 9180 的套件与 Go 1.26 版本。服务端为每个租户保存接收者私钥,应用用公钥做 HPKE 封装;密文记录套件、key version、租户和用途元数据,真正的租户/用途绑定放进 info 和 AEAD AAD。解密按版本读取旧私钥,轮换后新写入使用新版本,后台异步重加密。若需要发送方认证再选 Auth 模式;否则不伪造认证语义。测试覆盖篡改、跨租户、重放、随机性、旧版本和跨语言向量。”
分步骤深入解答
第一步:划分信封加密层
HPKE 通过 KEM 建立共享密钥,再由 KDF 派生密钥,AEAD 加密消息。信封记录的是封装公钥材料、算法套件、key version、nonce/序列化密文和公开元数据;根私钥及接收者私钥留在 KMS/HSM,不能写入密文或应用日志。
第二步:选择模式和身份语义
Base 模式提供接收者公钥加密但不认证发送方;PSK 模式额外要求预共享密钥;Auth 模式用发送方密钥证明来源。不要因为有平台登录身份就声称 HPKE 已认证发送方,登录授权与密码学模式是不同层。
第三步:定义上下文绑定
info 绑定协议、服务、版本和用途;AAD 绑定租户 ID、配置键、记录版本等需要完整性保护的元数据。解密时重新计算相同输入,任何租户、用途或版本替换都会让 AEAD 验证失败。
envelope = suite_id | key_version | encapsulated_key | aad_fields | ciphertext
info = "config-envelope/v1" | tenant_scope | purpose
aad = tenant_id | config_key | record_version第四步:设计密钥生命周期
每个 key version 有生成时间、状态、允许解密范围和销毁策略。轮换先发布新公钥,再切换写入版本;读取按 envelope 版本选择旧私钥。旧版本进入只读窗口,完成重加密和审计确认后才能撤销,避免先删钥匙造成不可恢复数据损失。
第五步:处理重放与序列化
AEAD 能检测篡改,不会自动阻止把一份合法旧密文重新写回。若配置有版本或单调序列,解密后由业务层检查 expected version;封装格式使用明确长度和版本字段,拒绝未知套件、截断数据和重复字段。
第六步:设计错误边界
对外可统一返回“无法解密”,内部审计记录结构化原因和 key version,但不能记录私钥、明文或完整密文。区分密钥不存在、格式不支持和认证失败有助于运维;对不可信调用者要避免通过错误差异泄露租户或密钥存在性。
第七步:建立测试与迁移
测试固定 RFC 向量、随机性、篡改单字节、跨租户 AAD、旧版本读取、轮换并发、撤销后失败和大消息分块。跨语言场景使用共享序列化和测试向量;Go 1.26 之前的服务通过兼容封装或升级门禁接入,不把实验 API 混入生产。
高质量示范回答
“我把 HPKE 当作封装层:KEM/KDF 建立并派生共享密钥,AEAD 保护配置明文。信封包含套件、key version、封装材料和密文;租户、用途、配置键与记录版本绑定到 info/AAD,防止跨租户复制。Base 模式只提供接收者机密性,需要发送方证明时才选 Auth 或上层签名。轮换采用新版本先写、旧版本只读、后台重加密、确认后撤销。AEAD 不防重放,所以业务层检查版本。测试覆盖篡改、旧版本、撤销、错误信息和跨语言向量,私钥只在 KMS/HSM 中。”
常见错误
- 把 Base 模式当发送方认证 → 接收者无法证明发送者身份 → 选择 Auth 或额外签名。
- 只把租户写在明文元数据 → 攻击者可跨租户替换 → 绑定到
info/AAD 并验证。 - 轮换后立即删除旧钥 → 历史数据无法读取 → 先只读兼容和重加密,再撤销。
- 认为 AEAD 自动防重放 → 合法旧密文仍可被重新提交 → 使用业务版本或序列检查。
- 把私钥放进配置或日志 → 加密边界被破坏 → 使用 KMS/HSM 与最小权限。
- 自定义无版本二进制格式 → 未来无法迁移和拒绝未知套件 → 显式版本、长度和套件字段。
- 暴露详细解密错误 → 泄露租户或密钥存在性 → 外部统一、内部审计。
- 只测成功路径 → 篡改和旧版本问题留到生产 → 加入向量、跨上下文和撤销测试。
追问及应对
追问一:为什么不用直接 RSA 或 ECDH 加密全部明文?
HPKE 明确组合 KEM、KDF 与 AEAD,适合封装对称会话密钥并处理大消息;直接用公钥算法加密任意明文容易遇到长度、随机性和模式误用问题。
追问二:何时选择 Auth 模式?
当接收者需要密码学证明某个已知发送方密钥参与加密时选择 Auth;只有接收者机密性需求时不应添加未管理的认证密钥。
追问三:info 和 AAD 有什么区别?
info参与密钥派生并定义协议上下文;AAD 不加密但由 AEAD 完整性保护,适合绑定随信封传输且解密时必须一致的元数据。
追问四:如何安全重加密?
读取旧版本并在同一事务或可重试工作流中写入新版本,保留幂等标记;只有新密文通过解密回读与审计校验后,才推进旧钥撤销。
追问五:如何防止密文替换到另一条记录?
把租户、配置键、记录版本和用途放入 AAD,并在读取时使用数据库当前值重建 AAD;替换后认证标签不匹配,解密失败。
追问六:HPKE 是否提供前向保密?
具体性质取决于密钥生命周期和模式。静态接收者私钥长期保留时,历史密文的保护取决于该私钥;需要更强前向保密时要设计临时密钥、轮换、销毁与协议级会话策略,不能只看 API 名称。