一、概述
"文件打不开"是取证中最常见的求助之一,但**"打不开"至少有五种完全不同的原因**,混为一谈会让整个处置方向跑偏:
| 现象 |
真正的原因 |
处理方向 |
| 磁盘整体读不出 |
卷加密(BitLocker / VeraCrypt) |
见 01、03 篇 |
| 整个分区是乱码 |
文件系统层加密 |
见 01、03 篇 |
| 单个文件打不开,但目录能列 |
应用层加密或文件损坏 |
本文 |
| 能打开但内容乱码 |
编码错配(UTF-8/GBK)或二次编码 |
用 file/uchardet 判断 |
| 根本没读全 |
截断、镜像不完整 |
回到成像环节 |
必须先建立一个概念区分:
- 卷/文件系统层加密(BitLocker、EFS、LUKS):数据落盘时整体加密,明文从不在磁盘上,密钥由系统统一管理。
- 应用层加密(MySQL TDE、SQL Server TDE、pgcrypto 应用层、SQLCipher):数据文件本身仍是普通文件,能列目录、能读文件头,只是某些表/列/文件的内容是密文。
后者是本文重点。
它有一个共同弱点:应用必须先读到密钥才能解密,所以密钥几乎总是以某种形式存在于配置或内存中。而应用层加密的密钥管理通常比卷加密薄弱得多。
这既是取证突破口,也是企业运维的常见盲区。
在取证流程中的位置:本篇属于分析环节的诊断分支,而且它的第一个动作是分类而不是破解——三步判定路径(文件头 → 加密层级 → 引擎与版本)必须在动手读数据之前走完。走错分支的代价不是浪费时间,是把一份损坏的文件当成加密文件上报,或者反过来。上游依赖是 BitLocker 全卷加密取证(先排掉卷加密)、EFS 文件系统加密取证(用户级透明加密的判定)与数据库取证通用方法(表空间与数据字典的常规路径)。密钥在内存中时,提取方法走 内存中的凭据与密钥;走不通时按 通用密码恢复 的优先级表退守。
本篇处理的证据形态,按引擎分四类,文件名与配置键是判定的第一手依据:
| 引擎 |
加密落在哪 |
密钥材料在哪 |
判据 |
| MySQL / MariaDB |
ibdata1 与各 .ibd 表空间的页内部分(文件头与空闲页仍是明文) |
keyring 文件(路径写在 my.cnf 的 early-plugin-load 与 keyring_file_data,插件为 keyring_file / keyring_encrypted_file) |
information_schema.innodb_tablespaces 与文件头的加密标志位 |
| PostgreSQL |
两条路径:文件系统加密(外部,见 02 篇)或 pgcrypto 应用层 |
pgcrypto 的密钥在会话里、密钥文件或环境变量 |
表里出现 \x... 开头的十六进制串 |
| SQL Server |
.mdf / .ndf 的页级 TDE |
主密钥 → 证书 → DEK 三层链,证书私钥受服务主密钥(DPAPI)保护 |
sys.dm_database_encryption_keys 的 encryption_state |
| SQLCipher / 应用自加密 |
整个库文件(含文件头) |
应用配置、sqlcipher.conf 或代码常量 |
文件头不是 SQLite 的 SQLite format 3,也不是明文,头 16 字节为随机值 |
配置文件这一侧的"伪加密"要单独看:base64 编码、gzip 压缩、自定义异或、以及 .pgpass、my.cnf 的 password=、~/.my.cnf、.env、Kubernetes Secret 的 base64 字段(base64 只是编码,不是加密,但 k8s 的 Secret 默认就是 base64,很多报告在这里写成"已加密"是错的)。Linux 上的 shadow 哈希属于认证哈希而非加密,走爆破路径而不是解密路径。
某个文件打不开的真实原因是哪一类、加密发生在哪一层、是哪种引擎与哪个版本、密钥材料是否存在于检材内的可及位置、在拿到密钥后能否实际解密并读出可读内容(这是唯一的定论标准,strings 看到可疑结构不算)。
密钥不在检材内就到此为止——本篇不提供任何密钥恢复或暴力破解手段,那些方法论在 通用密码恢复,且必须先确认授权范围。引擎与版本判断不出来时,所有偏移与结构结论都不成立,这是硬前提:同一引擎不同版本的密钥结构与解密方式都不同,套错版本会得到"能解密但全是乱码"的假成功。判定为加密不等于判定为恶意,企业的数据保护、备份加密、云厂商的静态加密都会产生同样的表象,这两者在证据上无法区分。阴性结果同样要限定范围:"在 my.cnf 与常见 keyring 路径下未发现密钥文件引用",未覆盖的是用户级 ~/.my.cnf、systemd 环境变量与容器内挂载的 Secret。Base64 出来的东西不等于解开了密文,用它凑出"可读"的结果是本领域最常见的一类假结论。
读者前提:需要能读十六进制并理解页与文件头的差别(为什么"能列目录能读文件头"就排除了卷加密);需要分清认证哈希、口令派生、对称加密这三类不同机制各自需要什么才能反推——这个区分错了,整个处置方向会偏到爆破上去;需要知道 MySQL、PostgreSQL、SQL Server 各自的数据文件与配置文件分别叫什么、各自的密钥管理机制完全不同;还需要掌握"结论必须经过实际读取验证"这条纪律,格式匹配与工具报成功都不构成定性依据。
二、核心原理
2.1 三步判定路径
① 是加密还是损坏? → 用对应工具的报错信息 + 文件头特征区分
↓
② 应用层还是文件系统层? → 能列目录 = 应用层;整块乱码 = 文件系统层
↓
③ 密钥来源在哪? → 配置文件 / 密钥环 / 注册表 / 环境变量 / 进程内存
第 ① 步的关键技巧:看报错信息,不要只看"打不开"。
| 工具 |
加密时的典型报错 |
损坏时的典型报错 |
sqlite3 |
file is not a database |
database disk image is malformed |
7z t |
Wrong password? / Data Error |
Headers Error / Unexpected end of archive |
openssl |
bad decrypt |
bad magic number |
mount |
wrong fs type |
wrong fs type(相同,不能区分) |
| 各类 GUI 解压 |
"需要密码" 对话框 |
"文件已损坏" 对话框 |
SQLite 的 file is not a database 既可能是加密也可能是损坏,不能凭这一条定性,必须看文件头。
2.2 用文件头判断"加密 vs 损坏 vs 编码"
损坏的文件几乎总是保留了原格式的文件头;加密的文件头则是随机的(除非加密方案保留了一段 magic)。
xxd -l 32 evidence.db # 看前 32 字节
| 文件头(hex) |
判断 |
53 51 4c 69 74 65 = SQLite f |
SQLite,正常 |
50 4b 03 04 = PK.. |
ZIP(docx/xlsx/apk) |
25 50 44 46 = %PDF |
PDF |
d0 cf 11 e0 |
旧版 Office OLE(.doc/.xls) |
52 61 72 21 = Rar! |
RAR 压缩包 |
ef bb bf / ff fe |
UTF-8 / UTF-16 BOM,是编码问题 |
| 全零或高熵随机 |
疑似加密或已损坏 |
熵值是辅助判据:正常文本/数据库文件通常在 4.0–5.5 bit/byte,加密块或压缩块接近 8.0(计算命令见第七节 1)。
取证纪律:熵值高只能说明"高度随机",不能单独定性为加密——压缩包、部分二进制格式、已损坏的文件同样熵值高。定性必须有第二个独立证据(工具报错、密钥材料存在、跨文件一致性)。
关于 SQLCipher 的重要更正:SQLCipher 没有固定 magic。它把 SQLite 前 16 字节当作 salt 随机化,因此表现为"文件头损坏的 SQLite"——前 16 字节随机,但偏移 16 字节起仍是合法的 SQLite 页结构(0x0D 0x00 0x01 等)。识别 SQLCipher 的正确方法是这个组合特征;任何声称"SQLCipher 有固定 magic"的说法都是错的。
2.3 MySQL / MariaDB 的表空间加密
MySQL 8.0 的 InnoDB 表空间加密(TDE)由 Keyring 组件/插件提供密钥管理,三层结构:
表空间(.ibd 文件) ──用 tablespace key 加密──> master key(主密钥)
└─ 存放在 keyring 文件里
关键取证事实:主密钥不在数据文件里,在 keyring 文件里。
拿到加密 .ibd 而没有 keyring,等于拿到一整块随机字节。
# /etc/my.cnf 或 /etc/mysql/my.cnf
[mysqld]
early-plugin-load=keyring_file.so # 必须在启动时加载,否则加密表打不开
keyring_file_data=/var/lib/mysql-keyring/keyring
# 新版本推荐:
early-plugin-load=component_keyring_file.so
component_keyring_file_data=/var/lib/mysql-keyring/keyring
keyring_file 插件自 MySQL 8.0.34 起已弃用,新部署用 component_keyring_file。取证时两种都要查,配置能直接区分实例用的是哪种。
-- 插件/组件是否加载 + 密钥文件实际路径
SELECT PLUGIN_NAME, PLUGIN_STATUS FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME LIKE '%keyring%';
SHOW GLOBAL VARIABLES LIKE 'keyring_file_data';
SELECT * FROM performance_schema.keyring_keys; -- KEY_ID 形如 INNODBKey-<uuid>-1
SELECT SPACE, NAME, SPACE_TYPE, ENCRYPTION
FROM INFORMATION_SCHEMA.INNODB_TABLESPACES WHERE ENCRYPTION = 'Y';
关于 performance_schema.keyring_file_instances:这是 Oracle Key Vault(OKV)组件的表(master_key_id、keyring_file_path 等字段),不是 keyring_file 插件。用插件时元数据在 performance_schema.keyring_keys(component 场景另有 keyring_component_status),密钥本体在 keyring_file_data 指向的文件里。这一点常被写错。
keyring 文件的初筛:keyring 是二进制容器,其中的密钥记录为 ASN.1/DER 结构的私钥,可用 openssl 试探(命令见第七节 3)。解析成功说明它确实是密钥材料,但不证明它能解开你的表空间——决定性验证见步骤 3。
2.4 PostgreSQL 的两条加密路径
PostgreSQL 没有内置整库 TDE,常见两条路径,取证时必须区分:
| 路径 |
机制 |
密钥位置 |
取证难点 |
| A. 存储加密 |
数据目录跑在文件系统层加密或 LUKS 上 |
挂载/启动时提供 |
低——与 01 篇相同 |
| B. 列级/应用层 |
pgcrypto 的 pgp_sym_encrypt(),或应用自行 AES |
密钥在应用配置或代码里 |
中——重点在这里 |
路径 B 的取证要点:pg_authid.rolpassword 保存的是(通常是 SCRAM/md5 的)账户口令摘要,不是 pgcrypto 的加密密钥。
pgcrypto 密钥的真实来源永远是应用配置——application.yml / settings.py / .env / config.php / appsettings.json。
grep -rE "pgp_sym_decrypt|pgp_sym_encrypt|pgcrypto" /mnt/image/opt/ 2>/dev/null
一个容易被忽略的高价值点:pg_stat_activity 的 query 列能看到当前连接的查询文本,其中可能残留解密函数调用与密钥:
SELECT pid, usename, application_name, client_addr, state, query
FROM pg_stat_activity WHERE state <> 'idle';
这是极高价值的现成明文,但它只在运行时存在,关机后就没了。
所以对数据库服务器,运行中/内存取证的优先级高于静态磁盘分析(见 内存中的凭据与密钥)。
2.5 SQL Server TDE
SQL Server TDE 的密钥体系是三层父子关系,取证必须从最外层的备份找起:
.bak 备份文件
└─ 内含受 service master key (SMK) 保护的证书私钥
└─ SMK 保护 database master key (DMK)
└─ DMK 保护表空间/列加密密钥 (DEK)
关键取证事实:.cer 证书必须与 .bak 一起被备份。
只拿到加密 .bak 而没有证书不可恢复——这是设计意图,防止 DBA 单独拿走数据。
SELECT EncryptionState, key_length, algorithm FROM sys.dm_database_encryption_keys;
SELECT name, thumbprint, pvt_key_encryption_type_desc, pvt_key_algorithm_desc
FROM msdb.dbo.master_keys; -- 私钥受密码还是 Windows 服务账户保护,决定后续要什么材料
导出证书(必须在原实例的克隆/恢复副本上做):
USE master;
OPEN MASTER KEY DECRYPTION BY PASSWORD = '<SMK 密码>';
BACKUP CERTIFICATE [TDE_Cert] TO FILE = 'C:\evidence\TDE_Cert.cer';
BACKUP SERVICE MASTER KEY TO FILE = 'C:\evidence\SMK.bak';
⚠️ 只读原则:BACKUP 会写文件。取证环境应使用原始实例的克隆或恢复副本,详见 BitLocker 全卷加密取证 中的只读要求。
2.6 应用自加密数据库:明文不在磁盘上
另一类"数据库加密"不依赖 TDE,而是库文件整体被应用层密码保护,典型是移动端与桌面客户端:
| 形态 |
机制 |
密钥来源 |
| SQLCipher |
用 PRAGMA key 把整个 SQLite 文件加密,磁盘上没有明文页 |
应用配置、代码常量、用户口令派生 |
| Realm / Core Data 加密 |
库文件自带密钥 |
应用 bundle 内的 key 文件 |
| 桌面端自建"库"(Navicat、DBeaver 等) |
见 06 篇 |
配置目录中的字段 |
这类库与 TDE 的取证逻辑正好相反:
- TDE 要找 keyring 文件(在文件系统里)。
- 应用自加密要找 **应用配置/代码/