Files
hms/crates/erp-core/src/crypto/hmac_index.rs
iven 7ab57ea1b2
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled
fix(health): PII 加密安全审计修复 — 2 Critical + 6 Medium + 4 Low
审计发现 55 检查点,46 PASS / 7 WARN / 2 FAIL,修复内容:

Critical:
- C1: 密钥轮换端点现在持久化新 DEK 到 tenant_crypto_keys 表
- C2: CachedDek 实现 Drop trait,释放时清零密钥材料

Medium:
- M1: 密文格式添加版本前缀 0x01,向后兼容旧格式
- M2: HMAC 索引使用独立子密钥,与加密 KEK 分离
- M4: 脱敏函数使用 chars() 迭代器,UTF-8 安全
- M5-M6: 医生执业证号详情响应脱敏 (mask_license_number)

Low:
- L1: dek_manager 改为 pub(crate),暴露 invalidate_dek() 方法
- L3: 合并 patient 列表搜索中冗余的重复 HMAC 计算
- L4: update_family_member/update_doctor 更新时设置 key_version
2026-04-26 13:34:25 +08:00

25 lines
787 B
Rust

use hmac::{Hmac, Mac};
use sha2::Sha256;
type HmacSha256 = Hmac<Sha256>;
/// HMAC-SHA256 搜索索引。使用 KEK 派生的独立子密钥,与加密密钥分离。
pub fn hmac_hash(key: &[u8; 32], value: &str) -> String {
let hmac_key = derive_hmac_key(key);
let mut mac = HmacSha256::new_from_slice(&hmac_key).expect("HMAC key length is valid");
mac.update(value.as_bytes());
hex::encode(mac.finalize().into_bytes())
}
/// 从 KEK 派生独立的 HMAC 子密钥,避免密钥复用
fn derive_hmac_key(kek: &[u8; 32]) -> [u8; 32] {
use sha2::Digest;
let derived = <Sha256 as Digest>::new()
.chain_update(b"pii-hmac-index-v1")
.chain_update(kek)
.finalize();
let mut key = [0u8; 32];
key.copy_from_slice(&derived);
key
}