"我的SQLite数据库加了密,结果原来的数据全没了,哪位大神救救我?"——这是我在技术群里看到过不下十次的提问。每次看到这种问题,我心里都有数:八成不是加密库不行,而是使用方式出了问题。SQLite加密这件事,看起来只是"打开数据库时多传一个密钥",实际踩进去才会发现它牵扯着编译选项、密钥派生、页格式、版本兼容、WAL模式、备份策略等一堆地雷。这篇文章我不打算只讲某个特定加密库的API怎么调,而是把整个SQLite加密问题拆开,从原理到选型,从迁移到排错,把我自己趟过的坑、调过的错误码、做过的性能压测都整理出来。如果你正准备给SQLite上加密,或者已经上了加密但经常遇到"打不开""数据损坏"这类问题,这篇文章大概率能帮你省几个通宵。
1. 为什么说SQLite加密不是"顺手加个密码"的事
1.1 明文落盘的SQLite到底有多"裸"
SQLite数据库本质上就是一个普通二进制文件。没有加密的情况下,你用任何十六进制编辑器打开它,直接就能看到里面的文本数据。更直接一点,用系统自带的字符串扫描工具,或者写个小脚本读一遍文件内容,表名、字段名、字符串类型的值全部一览无余。我第一次意识到这个问题是在一个本地工具项目里:我把用户的访问凭证、API Token、配置信息全部存在SQLite里,觉得"本地文件嘛,用户自己机器上能读自己的数据很正常"。结果某天我突然想,如果这个工具是通过U盘传播的,或者用户把数据库文件当作附件发给了别人,那这些明文凭证等于直接裸奔。SQLite不加密就落盘,就好比你给保险柜贴了个透明标签:里面放了什么,一目了然。
更隐蔽的风险是,很多开发者在日志、崩溃上报、临时文件里也会出现SQLite文件的副本。一旦某个副本被第三方拿到,数据就全量泄露了。这时候你再跟我说"我们的系统有登录权限、有沙箱隔离",统统没用——文件一旦离开你的控制范围,权限模型就失效了。SQLite加密要解决的核心问题,是把"文件被拷走"这件事的后果降到最低:就算对方拿到数据库文件,没有密钥,他看到的只是一团随机字节。
1.2 加密需求拆解:防拷贝、防篡改、防内存读取
但"防拷贝"只是第一个层次。我在做需求梳理时习惯把SQLite加密场景拆成三个独立维度,因为它们的实现代价完全不同。
第一个维度是防静态拷贝。这是最常见的需求,手段是数据库文件整体加密,或者页面级加密。所谓页面级加密,就是SQLite把数据按照固定大小的页(默认4KB或8KB)写入磁盘之前,先对页内容做加解密。读的时候先解密再交给SQLite引擎。这样数据库文件从头部到数据区全部是密文,无法通过文本分析提取任何信息。
第二个维度是防篡改。有些人担心攻击者不仅会读文件,还会改文件。比如修改账户余额、改权限标志。纯粹的加密并不防篡改——攻击者虽然不知道密钥,但可以随机覆盖页数据,导致数据库内容被破坏或行为异常。要防篡改必须在页级别加消息认证码(比如HMAC),或者在应用层维护关键字段的校验和。SQLite官方的加密接口本身不强制要求HMAC,是否支持要看具体的加密扩展实现。
第三个维度是防内存读取。进程运行时,SQLite在内存中持有的是解密后的页。如果一个攻击者能读取进程内存(比如通过调试器、注入,或者你的设备本身被攻破),那加密与否没有区别。SQLite的透明加密方案不解决这个问题,只能依靠系统层面(比如进程保护、内存混淆)来做缓解。我见过不少人把"数据库加密"和"数据绝对安全"划等号,其实它们之间差着十万八千里。
1.3 为什么很多人误以为"SQLite自带加密"
这是SQLite加密问题里最容易闹乌龙的一点。很多初学者看到SQLite的C语言API里有一个sqlite3_key函数(在某些第三方库中存在),就以为标准SQLite原生支持加密。但实际上,官方发布的SQLite源码里并没有内置加密模块,只有一套加密扩展接口(SQLITE_HAS_CODEC编译选项)。只有当你连上了某个实现了这套接口的加密库,sqlite3_key才会真正生效。
我见过最经典的翻车现场:某同事从网上找了一段"SQLite加密代码",在本地编译运行都正常,高高兴兴把数据库文件发给客户。结果客户那边用同样的代码打不开文件,报错file is not a database。后来才发现,那位同事编译的版本链接了某个加密库,而客户那边用的是标准SQLite,根本没设置密钥接口。两边文件格式完全不同,当然打不开。所以在谈SQLite加密之前,先确认自己用的到底是什么版本的SQLite、有没有开启SQLITE_HAS_CODEC、链接了哪个加密实现,这三者必须完全一致,否则后面全部白搭。
2. 从方案选型看SQLite加密的三种主流路线
2.1 自己包一层AES:十有八九会踩的坑
聊选型之前,我必须把"自己实现加密"这条路狠狠地劝退一次。最常见的做法是在应用层对敏感字段单独加密:先拿AES把某个字段的明文加密成密文,再存进SQLite。反而引出一堆新问题——最致命的是,加密字段在数据库里变成随机字节后,SQL的WHERE、ORDER BY、LIKE、JOIN这些操作全都不好使了。你说你想查"用户名等于alice"的人,可用户名被加密成了不可预知的密文,等于失去了索引和查询能力,只能把全表捞出来在内存里解密再过滤,性能直接雪崩。
还有同学会尝试"重写SQLite的虚拟文件系统,把所有读写都包一层加解密"。这听起来很完美,但实际做的时候你会发现自己要处理页缓存、页大小对齐、日志文件、WAL文件的加密、崩溃恢复这些底层细节。任何一个点没处理好,轻则性能极差,重则数据库损坏。我认识一位老哥,写了一个自研的SQLite加密VFS,测试环境跑得很好,一上生产就周期性出现"database disk image is malformed",排查了一个多月也没找到根因,最后整个项目推倒重来换成了成熟的加密扩展方案。所以我的建议非常明确:除非你是专门研究这个的,否则千万不要自己在底层造轮子。
2.2 透明加密扩展方案:对上层应用最友好的选择
所谓透明加密,就是在SQLite内核外面套一层加密逻辑:开发者的代码依然使用标准的sqlite3_open、sqlite3_prepare、sqlite3_step接口,只是在打开数据库之后、执行任何操作之前,额外调用一次sqlite3_key传入密钥。库内部的加密模块会在"页写入文件前"加密、"页从文件读入后"解密,上层SQL完全感知不到加解密的存在。
这种方案的好处显而易见。第一,它保留了SQLite的全部查询能力,索引仍然有效,优化器的工作不受影响。第二,迁移成本低,原有Dao层、ORM映射基本不用改。第三,加密粒度是整个数据库,不会出现漏掉某张表、某个索引的情况。
需要提醒的是,不同透明加密扩展产生的数据库文件格式是不兼容的。你用A库加密出来的文件,B库不一定能打开。而且即使同一个库,升级版本时也可能出现格式变化。选型不是选完就一劳永逸,你得有一个"加密库版本与数据库文件格式"的对应关系表,升级前先确认是否需要做数据迁移。我建议在所有涉及SQLite加密的项目里,第一件事就是写一份"数据库文件签名"记录:记录加密库版本、算法名称、KDF参数、页大小、SQLite版本。没有这张表,后面排查问题基本靠猜。
2.3 使用操作系统全盘加密:看似省事实则不能覆盖所有场景
很多人还会问:既然Android有全盘加密,iOS有文件保护,Windows有BitLocker,macOS有FileVault,那我是不是不需要对SQLite单独加密了?这个想法有道理,但覆盖范围不够。
操作系统的全盘加密保护的是"设备丢失后,整块磁盘被拆下来读数据"这种场景。它解决不了以下问题:如果你的App需要把数据库文件通过云盘同步、发给后端、做崩溃日志上传,这些文件一旦离开本机磁盘,全盘加密就失效了;如果攻击者是在你的应用运行时,通过注入代码把数据库文件的内存副本导出,全盘加密同样保护不了。换句话说,全盘加密是一道静态防线,SQLite层加密是一道细粒度防线。做本地优先、对隐私敏感的应用,我更倾向于两者都开,而不是指望某个单层方案包打天下。
下面把三种路线放在一起对比,方便你决策:
| 维度 | 应用层字段加密 | 透明加密扩展 | 操作系统全盘加密 |
|---|---|---|---|
| 对SQL功能影响 | 高,索引和查询基本作废 | 无,完全透明 | 无,加密在文件系统层 |
| 开发复杂度 | 中,需要自己维护加密逻辑 | 低,一个API调用 | 极低,系统配置即可 |
| 密钥管理 | 需要自己实现 | 需要自己实现 | 由系统处理 |
| 防文件被拷走 | 可以,但只能保护指定字段 | 可以,保护整个库 | 仅限本机磁盘,跨出系统即失效 |
| 对性能影响 | 中,每次访问都要解密字段 | 中低,页级加解密有开销 | 低,系统级性能损耗较小 |
| 适用场景 | 只有个别敏感字段 | 整个库都敏感,需要完整SQL能力 | 设备丢失场景,需要配合其他方案 |
3. 加密库接入实操:从打开一个库到顺利跑起来
3.1 库的选择与构建:别在版本上偷懒
选透明加密扩展时会遇到的第一道坎,是编译。大部分加密库不是官方SQLite发行版的一部分,你需要拿到对应的源码,在编译SQLite的同时启用SQLITE_HAS_CODEC选项。很多人在这一步就翻车了:编译时忘了定义SQLITE_HAS_CODEC,导致链接时报了一堆"未定义符号",或者程序能编译过,但运行时sqlite3_key怎么调都没反应。
我的习惯是编译完先做一个最朴素的验证:新建一个加密数据库,写入一条记录,然后强制用标准SQLite去打开它。如果标准SQLite能读到数据,说明加密没生效;如果报错或者读到乱码,说明加密扩展正常工作。这个验证一定要在刚开始就做,而不是等整个项目跑起来再去测。
另外,页大小是加密格式里一个容易被忽略的关键参数。SQLite的默认页大小是4096字节,但加密库在初始化时会根据配置生成页。如果你的加密库固定使用4096页大小,但你用PRAGMA page_size=8192创建的数据库去加密,就会产生不兼容的格式。更麻烦的是,有些加密库会在文件头写入自己的魔数和参数,导致不同版本之间互相不认。所以我的建议是:不要在同一个应用里混用不同版本的加密库,每次升级前先看官方变更日志,确认文件格式是否变化。如果你负责的App用户量大、老数据多,格式变化往往意味着你要写一个迁移工具,这个工作量不可小觑。
3.2 新建加密数据库与打开现有库的代码差异
不同加密库的API细节不同,但基本模式是一样的。我下面用伪代码演示,因为具体函数名取决于你用的库,关键是理解打开加密库的流程——新建和打开是两个完全不同的操作,混淆了它们就很难看报错。
# 伪代码,不同加密库接口略有差异 import sqlite3 def create_encrypted_db(db_path, key): conn = sqlite3.connect(db_path) # 设置密钥,此时数据库才真正进入加密状态 conn.execute("PRAGMA key = '{}'".format(key)) # 创建一个表并写入一条数据 conn.execute("CREATE TABLE demo(id INTEGER PRIMARY KEY, value TEXT)") conn.execute("INSERT INTO demo(value) VALUES ('hello')") conn.commit() conn.close() # 注意:关闭连接后,这个库文件已经是加密状态 def open_encrypted_db(db_path, key): conn = sqlite3.connect(db_path) # 打开时必须再次提供相同的密钥 conn.execute("PRAGMA key = '{}'".format(key)) # 如果密钥正确,下面这句正常执行 conn.execute("SELECT * FROM demo") return conn一个高频错误是:用PRAGMA key创建了加密库,然后后面每次打开时忘记调用PRAGMA key,导致收到file is encrypted or is not a database。还有一个错误是:把PRAGMA key放在创建表之后才调用。有些加密库要求PRAGMA key必须在打开工具之后、任何表和数据的读写之前执行。如果你先建了表再设置密钥,那么创建的表会以明文形式写入未加密的库,设置密钥后库文件变成加密态,但里面已经混入明文页,后续读取会异常。
3.3 明文库转加密库的正确姿势
把已有的明文SQLite库转成加密库,最忌讳的操作是:直接拿加密库扩展去打开旧的明文库,然后执行PRAGMA key,以为这样就把旧库加密了。实际上,PRAGMA key的作用是告诉加密模块"接下来读写这个文件时要加解密",它并不会对一个已有的明文文件做逆向加密。你需要先声明密钥,然后再读数据,而旧文件里的页根本没有加密,加密模块在读第一个页的时候就发现格式不匹配,直接报错。
正确做法有两种。第一种是,如果你的加密库支持附加数据库和导出功能,可以用"明文库作为attached库"的方式,把数据逐表迁入新的加密库。第二种是写一个迁移程序,从旧明文库逐表读取数据,再插入新加密库。
我迁移过几次,总结了一个稳一点的流程:
- 备份明文库文件,并且确认备份文件的大小和修改时间跟源库一致,别备份完自己心里没底。
- 用程序分别连接明文库和新建的加密库。
- 读取明文库的每一张表的建表语句,在加密库中先执行建表。
- 按表分批读取数据,每批几千行,批量插入加密库。这里一定要用事务包裹,不要每插一条
commit一次,否则速度会慢到怀疑人生。 - 迁移结束后,立即用正确密钥打开加密库,随机抽几张表做全量记录数和关键字段校验。
- 校验无误后,把原明文库文件物理删除或移动到隔离区域,避免残留副本泄露。
整个过程里,最容易忽略的是关联索引、自增序列、触发器和视图。因为数据迁移往往只关心数据本身,忘掉这些附属对象,会导致后续应用运行时出现"no such table"或"duplicate column name"之类的诡异错误。我建议在迁移脚本里自动读取sqlite_master表,把所有SQL字段的内容逐一在新库中执行,而不是只搬数据。
4. 密钥管理:SQLite加密里最容易翻车的半边天
4.1 硬编码密钥是最常见的反面教材
很多小团队的"加密"说白了就是面子工程:在代码里写一个字符串常量,比如"s3cr3t_key",然后把这个字符串当作密钥传给加密库。这种做法的保护能力几乎为零。逆向工程人员拿到APK或二进制文件后,用字符串搜索工具翻一遍,密钥就直接出来了。更讽刺的是,就算他们不逆向,你的密钥已经写进了版本管理的历史记录里,任何一个能访问代码仓库的人都能拿到。
正确做法是:不要把密钥作为字符串放在代码或配置文件里,而是放在操作系统提供安全存储区域,例如iOS的Keychain、Android的Keystore、Windows的DPAPI,或者使用硬件安全模块。对于跨平台桌面应用,至少也要把密钥放在用户目录下,并设置权限为仅当前用户可读,然后通过程序启动时从这些位置读取,再传给SQLite加密库。
4.2 用口令派生密钥:迭代参数不能乱填
如果你的应用是让用户输入PIN码或口令来解锁数据,那么你不能直接把用户口令作为加密密钥。口令的熵值通常很低,例如"123456"这种口令,直接作为密钥会被字典攻击瞬间破解。正确做法是使用基于口令的密钥派生函数(PBKDF2、Argon2、scrypt等),将用户口令加上一个随机盐,经过多次迭代计算后生成一个固定长度的密钥。盐可以明文保存在数据库文件旁边,但密钥不能。
迭代次数怎么定?我的经验是:桌面端可以定在十万到百万次级别,移动端根据设备性能在几万次左右,以每次解锁耗时不超过一两秒为上限。一定要避免默认值小到离谱的场景——我就见过某个项目用的是PBKDF2但迭代次数只设了1次,那跟直接拿口令当密钥其实没多大区别。
另外,你在用口令模式时一定要考虑"用户忘记口令"的情况。SQLite加密库通常没有"找回密码"功能,口令丢失意味着该数据库文件永远打不开。如果业务上需要找回,就必须在服务端实现密码重置逻辑,而这又会引入服务端能否解密客户端数据的信任问题。这是产品设计层面的事,但作为技术人员,你要在方案评审时把这个风险讲明白,不要等上线出事故了才发现。
4.3 密钥轮换:重加密不是改个密码那么简单
所谓密钥轮换,就是定期或者怀疑密钥泄露后,把数据库的解密密钥换掉。有些人以为密钥轮换就是"打开库,执行一条改密码的SQL",其实这是个重操作,因为新密钥要把数据库里所有的页都重新加密一遍。如果你的库有几十GB,或者处于低性能设备上,这个操作可能耗时很长,同时伴随着数据库文件的大量写操作。
加密库一般会提供类似rekey的指令,但它不等于瞬间完成,也不是简单改一行元数据。它内部要遍历所有页,用旧密钥解密再用新密钥加密写回。在rekey执行过程中,如果发生断电、进程崩溃、磁盘写满,数据库极有可能损坏,而且损坏后可能连用旧密钥都打不开了。我个人的铁律是:任何rekey操作之前必须先做完整备份,并且在操作完成后重新验证新密钥能否正常打开。如果条件允许,把rekey放在业务低峰期执行,并且保证设备电量充足、不要中途锁屏导致应用被系统杀掉。
5. 我踩过的三个加密后"打不开"坑及完整排查链路
5.1 症状一:密码明明正确,却一直报"file is not encrypted or is not a database"
这个报错是所有加密库用户最常遇到的。我碰到过一个真实案例:后台服务启动时报这个错,开发小哥百思不得其解,因为他确认密钥字符串没有改过。我让他先把数据库文件用十六进制工具打开看头部,结果发现标准的SQLite头(一串"SQLite format 3"字符)还在,但文件内容全部是随机字节。
进一步排查发现,他用的加密库升级了一个小版本,新版本默认的密钥派生算法参数变了,比如迭代次数从4000变成了100000。他用新版本的库去打开旧版本库,算法参数不匹配,解密结果自然就是乱码,加密库就认为这是个无效文件。解决方案是:升级加密库时,不要只换二进制,要先确认兼容性,同时保留旧版本的库,利用旧版本库重新导出数据,再导入新库。
完整的排查链路应该是:
- 记录当前加密库的版本号、默认算法、KDF参数、页大小。
- 想办法拿到创建该数据库时的配置信息(我之前让你建的那张"文件格式签名"表这时候就救命了)。
- 用加密库自带的命令行工具以交互方式打开文件,尝试各种参数组合。
- 如果仍然失败,回到最近一次成功的备份,确认备份文件能否打开。
- 如果备份都没有,那只能研究文件是否还存在明文页残留,用数据恢复工具碰碰运气,但希望渺茫。
5.2 症状二:重加密中途断电,数据库变成固定大小(比如原本几十MB变成1MB)
这种问题通常出现在做rekey或批量迁移数据时。SQLite为了保证原子性,很多操作是先在临时文件中完成的,但rekey这类底层页重写操作在不同加密库里的定义不一样。有些库是直接原位重写页,一旦写了一半断电,文件就处于新旧密钥交替的混合状态,SQLite的页结构完全被破坏。
我之前有个模拟项目X就在这个上面栽过:给一个约几十MB的加密库执行密钥轮换,结果办公室停电,起来后发现数据库文件大小从几十MB缩到了1MB,重新打开直接报损坏。当时的教训非常深刻:这类操作不是"以后可能有风险",而是"必然会伴随风险",必须当成一次线上变更甚至上线操作来对待。具体来说,要有可回滚的备份,要有变更前后校验(比如PRAGMA integrity_check、记录大小、记录数),最好还要有脚本能在变更后自动用新旧密钥各打开一次验证。
5.3 症状三:数据库能打开,但某些表数据乱码
这种情况比较隐蔽,往往出现在应用层加密码和数据库层加密混合使用的场景。之前有个同事,他原来的系统里对一些敏感字段已经用应用层AES加密了一次,后来为了统一安全策略,又把整个SQLite数据库切成了透明加密。迁移时他直接把旧库的加密字段密文当成普通数据导入了新库,结果新库用密钥能打开,但读到的字段是一堆乱码。原因很简单:字段有二重加密,他导入时没有按字段再单独解密一次。
排查这类问题,第一件事是把数据读取链路拆开:先看数据库层能不能正常读出完整字节,再看应用层解码后是否符合预期格式。如果数据库层读出来的字节与源库一致,那问题就在应用层。如果数据库层读出的字节本身就不对,说明可能在迁移过程中字节被转码修改过。这种乱码问题往往不致命,但它提醒我们:加密方案的变更不能只关注"能不能打开文件",更要关注"打开后应用是否还能按预期解析数据"。
5.4 排查思路小结
这里我把SQLite加密问题的排查方法论总结一下,按优先级排序:
- 先确认文件本身没有被截断或篡改(比较大小、修改时间、哈希)。
- 再确认加密库版本和配置参数与写入时一致。
- 再确认密钥内容没有被意外的空白字符、换行符污染。
- 然后用加密库自带的验证工具做测试,避免应用层包装代码干扰。
- 最后回头检查备份,看备份是否存在同样的症状,借此判断问题是出在库里还是出在使用方式上。
这一套流程走下来,绝大多数"打不开"的问题都能定位到具体环节。真正让人绝望的往往不是排查太复杂,而是没有记录参数、没有备份这两个大前提下,再巧的排查思路也是无米之炊。
6. 加密之后的性能、WAL模式与备份恢复
6.1 性能损耗:测过才有说服力
很多开发者担心加密会带来严重的性能下降。我的实测经验是,这个影响确实存在,但没有到不可接受的程度。我在某个工具里对同一个约500MB的数据库做过对比:读操作(大量SELECT)加密后耗时大约增加20%到40%,写操作(频繁INSERT、UPDATE)增加约30%到50%,具体数字取决于CPU是否支持AES硬件加速以及页缓存命中率。
需要留意的是,加密后每次读取一个页都要解一次密,因此页缓存命中率对性能影响非常大。如果应用访问模式是"小范围反复读取",缓存命中率高,加密影响就不太明显。如果是全表扫描或者大量随机读,加密开销就会被放大。我还有一个体会:加密对IO次数的影响并不大,主要是CPU的开销。因此在低配硬件上,如果性能吃紧,优先考虑升级CPU能力或优化查询模式,而不是放弃加密。
加密库本身也可能提供性能开关,比如是否开启硬件加速、是否调整KDF次数。但KDF次数直接影响安全性,不建议为性能无限调低。平衡点取决于你的风险承受能力,至少要保证计算量能抵御在线字典攻击和离线暴力破解。
6.2 WAL模式与加密库的兼容性
默认的SQLite日志模式是DELETE模式,写操作先写旧页副本到回滚日志,再修改主数据库。加密库在DELETE模式下相对好处理,因为日志文件中的数据页也需要加密,但加密库有自己的处理方式。而WAL模式下,事务记录先写入独立的-wal文件,之后通过checkpoint合并到主库。这意味着不仅主库文件需要加密,WAL文件同样需要加密,而且主库和WAL文件必须共享相同的密钥和格式。
我遇到过的兼容性坑,是在一个跨平台项目里:同一套数据,在Windows上创建并使用后,拷贝到macOS上打开,偶尔出现database disk image is malformed。最后发现是WAL模式下,文件拷贝时没有把-wal和-shm文件一起拷贝,导致目标机上WAL上下文缺失,触发了一致性检查。解决方法是:跨设备拷贝SQLite数据库前,先安全关闭连接,或执行PRAGMA wal_checkpoint(TRUNCATE)把WAL内容合并回主库,然后把主库文件单独拷走。对于加密数据库,这个建议同样成立,而且更严格——万一WAL文件和主库密钥状态不一致,恢复起来比明文库麻烦得多。
6.3 备份与恢复的特殊姿势
SQLite加密数据库的备份,不能简单地把文件另存一份就完事。第一,如果你正在使用WAL模式,直接拷贝主库文件很可能丢失WAL中尚未合并的事务。第二,加密库的备份文件与密钥绑定,如果密钥丢失或变更,备份也失去意义。第三,备份过程中如果应用还在写库,文件可能处于不一致状态。
我建议的备份姿势是:
- 应用层先进入一个"静默模式",暂停所有数据库写操作。
- 执行
PRAGMA wal_checkpoint(TRUNCATE)把WAL中数据合并回主库。 - 使用SQLite的在线备份API(
sqlite3_backup)创建一致性的备份文件,而不是直接复制文件。 - 备份完成后,尝试用当前密钥打开备份文件,执行
PRAGMA integrity_check,确保备份可恢复。
恢复时也要注意:先按原密钥打开备份文件,验证完整性;如果需要迁移到新环境,再用同样的密钥拷贝到新位置开库。如果你因为密钥管理失误导致密钥不符,那任何备份都等于不存在。
最后再聊一个实用小技巧:开发调试时,为了节省时间,可以临时创建一个未加密的开发库,方便直接查看SQL执行结果。但生产环境一定不要出现明文库。我习惯在代码里加一个启动检查:检测到数据库文件头部是明文SQLite格式且当前环境标记为生产环境,直接拒绝启动。这个防御机制成本很低,但能防止某个临时改Bug的同事把生产库泄露成明文。
SQLite加密这件事,最折磨人的不是"加密"本身,而是密钥生命周期管理和各种"理论上不该出现但确实出现"的意外。我自己的长期教训就是:做任何加密库变更前,先备份;备份之后,立刻验证恢复;验证恢复成功之后,才允许自己动下一步。如果你能逼自己养成这个习惯,那些"加密后数据全没了"的求助帖,大概永远不会换成你来发。