一、拦截的精确位置:IRP 类型与缓存语义
Windows 平台上的文件系统过滤驱动通过 Minifilter 框架注册 PreOperation 和 PostOperation 回调,拦截的是 I/O 请求包(IRP),而非应用层的 ReadFile/WriteFile 调用。这一区分在工程上至关重要:同一份文件数据可能以不同的 IRP 路径到达磁盘,过滤驱动必须对每一种路径给出正确的加解密决策。
核心的 IRP 类型及其系统调用映射如下:
IRP 类型 对应系统调用 透明加密中的职责
IRP_MJ_CREATE NtCreateFile / NtOpenFile 判定是否受信进程,决定是否附加加密上下文
IRP_MJ_READ NtReadFile 从磁盘读入密文后,在返回上层前解密
IRP_MJ_WRITE NtWriteFile 将上层明文加密后写入下层磁盘
IRP_MJ_SET_INFORMATION NtSetInformationFile 处理文件长度变更、重命名、删除等元数据操作
IRP_MJ_CLEANUP NtClose 释放句柄关联的加密上下文
仅仅拦截 READ 和 WRITE 是不够的。IRP_MJ_SET_INFORMATION 中的文件长度变更(EndOfFile)直接与加密头的存在耦合:磁盘上的密文文件比其逻辑明文多出一个加密头,任何截断或扩展操作都必须在驱动层进行偏移量换算,否则应用看到的大小与磁盘上的实际字节数不一致。这是透明加密在元数据层面最容易被忽略的一致性约束之一。
更微妙的问题来自系统缓存。Windows 的 Cache Manager 可能在过滤驱动之下缓存文件页。如果驱动在 READ 路径上解密了数据,但缓存中保留的仍是密文页,后续读取将拿到未解密的内容。常见的处理方式包括绕过缓存(FLTFL_IO_OPERATION_NON_CACHED)或在写路径完成后主动刷新缓存,但前者牺牲读性能,后者引入额外的 I/O 开销,需要在具体负载下做取舍。
二、磁盘布局:加密头作为密钥元数据载体
透明加密文件的磁盘形态不是“纯密文”,而是“加密头 + 密文正文”。加密头的存在解决了一个根本问题:文件脱离策略服务器后,过滤驱动需要在不依赖网络查询的前提下,仅凭文件本身完成密钥定位和解密判定。
一个具有代表性的加密头结构包含六个字段:
Flags:加密文件标识,用于在 IRP_MJ_CREATE 阶段快速判定该文件是否处于加密管理之下;
Algver:加密算法版本标识,为算法轮换和异构终端共存提供前向兼容空间;
FECK:文件加密密钥的密文(File Encryption Key Ciphertext),即用上层公钥或 KEK 封装后的 DEK;
Timestamp:加密时间戳,服务于审计和密钥生命周期管理;
Validation:头部完整性校验值,防止攻击者篡改算法标识或密钥索引以诱导驱动执行错误解密路径。
这个结构的工程意义在于:密钥元数据与密文正文共存于同一文件,文件本身就是一个自包含的密码学对象。代价是头部字段成为攻击面的一部分——Validation 字段的校验强度直接决定了驱动是否会信任被篡改的 Algver 或 FECK。
三、密钥分层:DEK、KEK 与数字信封的密码学构造
文件级 DEK 模型的动机是隔离性:单个文件的密钥泄露不应波及同目录或同用户的其他文件。但在密码学上,DEK 本身必须被保护,否则攻击者直接从加密头中读取明文 DEK 即可。这就引入了分层封装。
在国密体系下,典型构造是数字信封:文件正文由 SM4 以 DEK 加密;DEK 本身由 SM2 公钥(或 KEK)封装后放入 FECK 字段。读取时,驱动用对应的 SM2 私钥或 KEK 解开 FECK,得到明文 DEK,再用于解密正文。SM3 则承担头部完整性和密钥校验的哈希职责。这套“SM4 加密数据、SM2 封装密钥、SM3 校验完整性”的组合,与 AES-GCM 或 RSA-OAEP 在密码学结构上属于同一类信封模型,区别仅在于算法族的选择。
这里有一个容易被忽视的工程约束:DEK 在驱动内存中的驻留时间。过滤驱动在每次 READ/WRITE 时都需要 DEK 的明文形式,如果每次操作都从 FECK 解封一次,SM2 私钥运算的开销在频繁小 I/O 场景下会迅速累积。合理的实现需要在内核态维护一个受保护的 DEK 缓存,但其生命周期必须与文件句柄或进程上下文绑定,并在 IRP_MJ_CLEANUP 时可靠释放。缓存泄露或过期不释放,是驱动层密钥管理中最典型的内存安全风险。
四、受信进程判定:不是“认进程名”,而是“认代码身份”
驱动层看到的是 IRP,而不是发起 IRP 的“意图”。判断当前进程是否应获得明文,本质上是代码身份验证问题。
实践中,单纯的进程名或路径匹配是不够的,因为可执行文件可以被替换或重命名。更稳健的方式是对可执行体提取多因子指纹——文件哈希、数字签名、路径、版本信息的组合——并与内核态维护的白名单哈希表比对。WinDlp 的实现还提供了一个性能优化思路:不哈希整个二进制(多 GB 的文件会导致启动延迟),而是仅读取 PE 头部的前 4 KB 计算轻量指纹,利用 PE 结构在文件前部的稳定性来平衡准确率与开销。
但这个机制有一个根本性的逻辑边界:受信进程判定只能回答“这个代码是不是已知的可执行体”,无法回答“这个可执行体当前的行为是否被恶意注入或劫持” 。如果一个受信进程被 DLL 注入或远程线程劫持,过滤驱动仍然会将明文递交给这个被污染的进程上下文。这是驱动层方案与 EDR 类行为监控方案之间的天然分工缝隙。
五、文件系统语义的硬点:内存映射、原子性与多进程句柄
真正让透明加密在研发场景中“翻车”的,很少是密码学,而是文件系统语义的细粒度处理。
内存映射文件(Memory-Mapped File) 是最典型的例子。编译器、链接器和部分代码编辑器通过 CreateFileMapping + MapViewOfFile 访问文件,数据经由页错误直接从 Cache Manager 的页面进入进程地址空间,绕过了常规的 IRP_MJ_READ 路径。如果过滤驱动只在 READ 回调中解密,内存映射读取拿到的将是磁盘上的原始密文。处理方式通常需要结合 IRP_MJ_ACQUIRE_FOR_SECTION_SYNCHRONIZATION 等回调,或在创建 section 时对底层文件进行预解密/重定向,其复杂度远高于顺序 I/O 路径。
写入的原子性与崩溃一致性 是另一个硬点。加密头的 Validation 字段需要在每次文件修改后重新计算,但如果校验值的更新与正文写入不是原子的,掉电或崩溃可能导致头部与正文不一致,进而使文件在下次打开时被驱动判定为损坏而拒绝解密。一种务实的折中是:头部完整性校验仅覆盖密钥元数据字段(Algver、FECK),不覆盖正文哈希,正文完整性交由上层审计或备份机制兜底。这一取舍降低了写入路径的复杂度,但也意味着驱动无法检测正文层面的静默篡改。
多进程句柄共享 则要求加密上下文不能简单地绑定到单个进程。一个受信进程打开的加密文件句柄可以被复制给另一个进程(例如 DuplicateHandle),如果第二个进程是非受信的,驱动必须在句柄复制路径上重新评估授信状态,否则明文句柄会通过句柄继承泄露给非授信上下文。
六、离线场景:票据缓存是一个密码学与会话管理的交叉问题
设备离开企业网络后,策略服务器不可达。解密授权不能依赖在线查询,通常的做法是客户端缓存一个带时效的授权票据,在离线窗口内以本地票据作为解封 DEK 的凭据。
从密码学角度看,离线票据本质上是一个短时效的 KEK 封装:票据中携带(或以票据派生密钥封装)了解封 FECK 所需的材料,其有效期由票据内的签名时间戳和客户端单调时钟共同约束。工程上的关键参数是离线窗口长度:窗口越长,移动办公连续性越好,但设备丢失后密文的可读时间也越长。可行的设计是将窗口与终端安全状态绑定——例如 TPM 度量的启动完整性或设备绑定密钥——使得票据即使被复制到另一台机器也无法使用。这已经超出了单纯的文件加密范畴,进入了终端身份与密钥绑定的交叉领域。
七、能力的边界:驱动层透明加密不做什么
技术上的诚实性要求明确这套机制的能力半径。
它保护的是“文件作为文件离开授权环境后不可读” ,而不保护数据以非文件形态的流失。剪贴板复制、截屏、打印、屏幕拍照、人工转录——这些通道不经过文件系统过滤驱动的判定路径。配套的剪贴板管控(内核级剪贴板钩子或剪贴板加密)和截屏防护(图形栈拦截或窗口渲染保护)是独立的技术栈,与文件加密共享受信进程判定逻辑,但拦截点完全不同。
它不防御内核态攻击。一个以 Ring 0 权限运行的恶意驱动可以直接读取磁盘扇区或篡改过滤驱动的回调逻辑,透明加密对此没有结构性防护。它的安全模型假设操作系统内核本身是可信的。
它不解决“人”的问题。如果受信进程被授权查看明文,那么坐在屏幕前的人就可以用任何非技术手段将信息带出。加密抬高的是文件级复制的门槛,不是信息扩散的门槛。
迪康Defender所代表的驱动层透明加密路径,其技术内核可以概括为一组精确的工程决策:在 Minifilter 的 IRP 回调链中选择正确的拦截点,用自包含的加密头承载密钥元数据,以数字信封结构分层保护 DEK,并通过受信进程指纹和离线票据将“谁能在什么条件下看到明文”编码为可执行的策略。这条路径的代价分布在内核态开发的每个细节里——缓存一致性、内存映射语义、写入原子性、句柄生命周期——而它的收益只有一个,但足够根本:安全属性被绑定到文件的字节本体上,文件走到哪里,密文状态就跟到哪里。判断一套方案是否成熟,不在于它宣称支持哪些算法,而在于它对上述每一个硬点给出了什么样的、可被工程验证的回答。