某某透明文档加密软件的解密方案
某某透明文档加密软件的解密方案
Section titled “某某透明文档加密软件的解密方案”有些透明文档加密软件会让同一个文件呈现两种状态:在 Word 中可以正常打开,换一个普通读取程序,拿到的却是密文。文档内容是否可读,取决于客户端如何处理这次文件访问,以及发起访问的进程是否匹配管理员配置的规则。
这次整理的方案就从这个差异入手:让一个简单的读取程序使用策略中允许的进程名称,取得客户端返回的明文,再把明文保存到文件中。本文隐去具体产品名称,记录这类规则下已经验证的做法。
透明加密发生在文件 I/O 路径上
Section titled “透明加密发生在文件 I/O 路径上”透明文档加密通常在应用与文件系统之间加入过滤逻辑。应用仍然调用普通的文件打开、读取和写入接口,客户端在这些 I/O 操作经过时检查策略,并决定是否执行加解密。
可以把它理解为在内核的文件 I/O 路径上加一层拦截逻辑。更准确的实现方式包括文件系统过滤驱动或挂钩,并不一定是直接修改内核代码、给内核“打补丁”。例如,Windows 的文件系统 minifilter 是内核态组件,可以观察、修改或阻止文件 I/O;微软也把磁盘数据的自动加解密列为典型用途。微软文件系统过滤驱动文档
下面是按进程策略返回不同内容的一种工作模型:
磁盘上的加密文档 │ ▼文件 I/O 过滤逻辑检查访问进程与策略 │ ├── 匹配明文读取规则 → 客户端解密 → 应用取得明文 │ └── 不匹配 → 返回密文或拒绝访问,取决于策略不同系统和产品版本的实现可能不同。macOS 也在逐步以系统扩展替代部分旧内核扩展,不能把所有版本都归结为同一种内核补丁。Apple 内核扩展与系统扩展说明 本次验证观察到了按应用名称读取不同内容的行为,没有逆向确认客户端内部使用的具体内核接口。
为什么一个名为 WORD 的读取程序能取得明文
Section titled “为什么一个名为 WORD 的读取程序能取得明文”管理员后台通常会配置哪些应用参与透明加解密。假设某条规则允许名为 WORD 的进程读取 Word 文档的明文,并且客户端判断进程身份时采用的就是这个名称,那么一个名为 WORD 的自定义程序也可能匹配这条规则。
这个程序不必具备 Word 的编辑、排版或渲染功能,只需要打开文件并读取字节。客户端会在读取路径上完成解密,程序收到的已经是明文。
这里的 WORD 是示例,实际名称必须以管理员后台的配置为准。在不同平台上,配置可能使用 Microsoft Word、WINWORD.EXE 或其他名称。可执行文件名、进程名与应用显示名也不一定相同,不能只看图标下的文字。
本次环境中,使用对应应用名称启动一个 Go 读取程序,已经能够取得文档明文。这个结果只适用于已验证的规则:如果客户端还校验完整路径、签名或其他进程身份信息,仅修改文件名就未必有效。客户端本身也必须处于能够解密该文件的状态。
用 Go 做一个最小读取程序
Section titled “用 Go 做一个最小读取程序”下面的程序只接收一个文件路径,把读取到的字节原样写到标准输出:
package main
import ( "fmt" "io" "os")
func main() { if len(os.Args) != 2 { fmt.Fprintln(os.Stderr, "usage: reader input-file") os.Exit(1) } if err := read(os.Args[1]); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) }}
func read(path string) error { f, err := os.Open(path) if err != nil { return err } defer f.Close() _, err = io.Copy(os.Stdout, f) return err}保存为 reader.go。假设策略匹配的进程名称确实是 WORD,编译并运行:
set -o pipefailgo build -o WORD reader.go./WORD "input.docx" | cat > "output.docx"如果配置的名称含空格,编译与执行时都需要加引号,例如:
go build -o "Microsoft Word" reader.go./"Microsoft Word" "input.docx" | cat > "output.docx"这里让读取程序输出到管道,再由普通的 cat 进程把字节保存到输出文件,相当于把客户端返回的明文“另存为”。set -o pipefail 让读取端失败时,整个管道也返回失败状态;已经创建的输出文件仍需检查。输入和输出使用不同路径,先保留原文件。不要把输出重定向到输入路径:Shell 会在读取程序启动前截断输出文件,导致原始内容丢失。
写入也受透明加密策略影响。本次实测中,同一个读取程序输出到管道时拿到的是有效 DOCX 内容,直接把标准输出重定向到 .docx 文件却会再次得到密文;改为通过普通 cat 进程保存后,输出通过了 ZIP 校验。读取程序直接写入不带文档扩展名的临时文件也通过了校验。这说明读取时取得明文与最终保存明文是两个需要分别验证的步骤。
如果目标目录或普通写入进程也触发了加密规则,上面的方式仍可能得到密文。正式工具可以由普通主进程准备临时输出文件,读取副本写出结果,再由主进程验证并决定是否替换源文件。
从读取示例整理成单个 Go 工具
Section titled “从读取示例整理成单个 Go 工具”旧实现可以由脚本调度:按扩展名选择应用名称,生成读取程序,编译后运行,再接收结果保存。这个过程可以全部放进一个 Go 可执行文件,避免每次运行都依赖脚本解释器和编译器。
程序启动时作为普通主进程,按需把自身复制到临时目录,使用策略对应的名称启动副本。主进程创建临时输出文件,把打开的文件句柄作为副本的标准输出;副本进入内部读取模式,读取文档并写出结果。主进程等待副本结束,再检查结果和替换源文件。两种角色共用同一个编译产物,运行时无需再编译。
主程序按扩展名选择读取进程名称 → 复制自身并启动读取副本 → 客户端向副本提供明文 → 副本通过标准输出写入临时文件 → 主程序验证结果后保存扩展名在这里用于选择策略中的应用名称,例如 .docx 对应 Word 的读取规则。它不代表文件一定加密,也不决定加密算法。
批量处理可以提供两个入口:
macsec-decrypt decrypt input1.docx input2.pdfmacsec-decrypt scan /path/to/directorydecrypt 处理指定文件,成功后替换原路径;scan 递归读取文件头、识别加密文件,列出路径和支持状态,用户确认后才批量处理。扫描依据应来自实际加密样本的文件格式,Finder 的锁图标只能作为界面提示。未知扩展名的加密文件仍应列出,避免把“程序不会处理”误认为“没有加密”。
原文件替换前需要验证什么
Section titled “原文件替换前需要验证什么”从客户端读到了数据,并不能直接判定解密成功。没有匹配规则时,读取操作也可能成功,只是拿到的仍然是密文。
工具先把结果写入源文件同目录的临时文件,检查读取进程的退出状态、结果非空、已知加密头是否消失,以及内容是否与原始密文不同。对于 .docx,还可以检查 ZIP 结构与 CRC,并确认存在 word/document.xml;PDF 则需要继续检查格式或用阅读器确认内容。
unzip -t "output.docx"非空、内容变化和文件头检查只是初步筛查,不能单独证明文档完整。保存后还需要用普通读取方式检查输出,避免在透明解密视图里把再次加密的文件误判为明文。
替换前也要重新检查源文件的身份、大小、修改时间和内容摘要。如果源文件在处理期间发生变化,取消替换;验证通过后,再通过同目录重命名替换原路径。这样不会因为读取失败就把原文件写成空文件,也不会用旧读取结果覆盖处理中发生的修改。
本次在 macOS 上使用真实样本副本验证了 PDF、DOCX 的明文读取与原路径替换,DOCX 的 ZIP CRC 校验通过。扫描取消、损坏密文保留、源文件在处理中变化时取消替换也已验证。其他扩展名对应的进程规则、其他加密版本和内部驱动实现没有逐一验证。
替换文件时,普通权限位和修改时间可以单独保留,但创建时间、扩展属性、ACL 和原 inode 不会自动继承。批量工具还需要报告单个文件失败并继续处理剩余项,让保存结果和失败原因都可见。