数据库与配置文件加密的识别与解密

"文件打不开"是取证中最常见的求助之一,但"打不开"至少有五种完全不同的原因,混为一谈会让整个处置方向跑偏:

最后更新 2026-10-09版本 v1.0维护 DigiForensics查看历史 0

关键词:透明数据加密、TDE、MySQL keyring、pgcrypto、TLS、CBC、kubeconfig、云凭据、base64 误判 难度:进阶 前置知识:数据库基本概念、文件系统加密与卷加密的区别、Base64 与对称加密基础、SQL 基础

一、概述

"文件打不开"是取证中最常见的求助之一,但**"打不开"至少有五种完全不同的原因**,混为一谈会让整个处置方向跑偏:

现象 真正的原因 处理方向
磁盘整体读不出 卷加密(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 的取证逻辑**正好相反