一、为什么要给SQLite加加密功能
很多做小项目的开发者,比如做本地笔记APP、嵌入式设备的存储模块,或者轻量级桌面工具的,都会用到SQLite。它不用装服务、单文件就能存数据,用起来特别省心。但它有个硬伤——默认存数据的文件是明文的。要是有人拿到这个.db文件,直接用文本编辑器打开,就能看到里面的账号密码、隐私内容,完全没隐私可言。
给SQLite加加密,最省心的办法就是用现成的扩展,不用自己从零写加密逻辑。市面上最常用的就是SEE(SQLite Encryption Extension),还有一些其他的开源扩展,比如SQLCipher、wxSQLite3。选哪个,直接决定了开发难度和程序跑起来的速度,接下来就一步步说怎么选,以及实际用起来的性能损耗到底有多大。
二、主流SQLite加密扩展的选型对比
选扩展不能光听名字,得从能不能免费商用、好不好集成、兼容性这几个核心点来比,不然选完了要么踩版权坑,要么改代码改到崩溃。
2.1 各扩展的核心特点
先给大家列三个主流扩展的基本情况,方便快速筛选: 第一个是SEE,它是SQLite官方出的加密扩展,相当于官方“亲生”的,兼容性拉满,能完美适配各个版本的SQLite,不会出现改个版本就用不了的情况。但它是收费的,个人非商用可以免费,要是做付费APP或者商用项目,得买授权,成本是个问题。 第二个是SQLCipher,这是开源的,用的是MIT协议,只要保留原作者的版权声明,免费商用完全没问题,很多中小项目都在用。它的加密逻辑是自己实现的,和SQLite核心是分开的,更新SQLite版本的时候,只要SQLCipher没改核心接口,基本不用改太多代码。 第三个是wxSQLite3,也是开源的,专门给C++写的,很多用C++做桌面程序的开发者会选,它把加密和SQLite核心集成得比较深,用起来很顺手,但如果是用其他语言比如Python、Java的话,集成起来会麻烦点。
2.2 选型的核心判断标准
选的时候按这三个标准来,基本不会踩坑: 第一,版权要求:如果是个人项目或者非商用,SEE完全够用;如果是商用项目,优先选SQLCipher这种开源免费的,避免后续版权纠纷。 第二,开发语言:如果用C/C++,三个都能选;如果用Python、Java、Go这类,优先选SQLCipher,因为它有很多第三方封装好的库,不用自己写绑定。 第三,版本兼容性:如果你的项目要长期维护,经常更新SQLite版本,选SEE,毕竟是官方的,适配速度最快;选SQLCipher的话,要注意它的版本和你用的SQLite版本对应,不然会出兼容性问题。
三、性能损耗实测对比(含完整示例)
光说特点没用,得实际测测,加了加密之后,程序跑起来到底慢多少。这次测的是三个最常用的操作:插入1万条数据、查询1万条数据、更新1万条数据,用的是同一个测试环境,排除其他干扰。
3.1 测试环境说明
测试用的是Windows 10系统,CPU是i5-10400,内存16G,用的是Python 3.10,因为Python是跨平台的,示例代码通用,而且集成SQLCipher和SEE都很方便。 测试的代码统一用Python栈,先给大家放测试用的示例代码,所有操作都加了注释,方便大家自己跑一遍:
# 测试用的Python栈,集成了pysqlcipher3(SQLCipher的Python封装)和pysqlite3(未加密的原生SQLite)
import time
import sqlite3
from pysqlcipher3 import dbapi2 as sqlcipher
# 测试函数:测试不同SQLite操作的耗时
def test_performance(db_type, db_name, operation, data_count=10000):
# 连接数据库,SQLCipher需要设置加密密钥,原生SQLite不用
if db_type == 'sqlcipher':
conn = sqlcipher.connect(db_name)
conn.execute("PRAGMA key = 'test_key_123'") # SQLCipher设置加密密钥的语句
else:
conn = sqlite3.connect(db_name)
cursor = conn.cursor()
# 先创建测试表,不管测什么操作都需要
cursor.execute("""
CREATE TABLE IF NOT EXISTS test_data (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
age INTEGER,
content TEXT
)
""")
conn.commit()
start_time = time.time() # 记录操作开始时间
# 测试插入操作
if operation == 'insert':
for i in range(data_count):
cursor.execute("""
INSERT INTO test_data (name, age, content)
VALUES (?, ?, ?)
""", (f'name_{i}', i, f'content_{i}_test'))
conn.commit()
# 测试查询操作,先插入数据再查询
elif operation == 'select':
# 先插入数据,避免空查询
for i in range(data_count):
cursor.execute("""
INSERT INTO test_data (name, age, content)
VALUES (?, ?, ?)
""", (f'name_{i}', i, f'content_{i}_test'))
conn.commit()
cursor.execute("SELECT * FROM test_data")
cursor.fetchall() # 取所有结果
# 测试更新操作,先插入数据再更新
elif operation == 'update':
for i in range(data_count):
cursor.execute("""
INSERT INTO test_data (name, age, content)
VALUES (?, ?, ?)
""", (f'name_{i}', i, f'content_{i}_test'))
conn.commit()
cursor.execute("UPDATE test_data SET content = 'updated_content' WHERE age < ?", (data_count,))
conn.commit()
end_time = time.time() # 记录操作结束时间
conn.close()
# 打印测试结果
print(f'{db_type} 执行{operation}操作耗时:{end_time - start_time:.2f}秒')
# 执行测试,先测原生SQLite,再测SQLCipher
test_performance('native', 'native_test.db', 'insert')
test_performance('sqlcipher', 'sqlcipher_test.db', 'insert')
test_performance('native', 'native_test.db', 'select')
test_performance('sqlcipher', 'sqlcipher_test.db', 'select')
test_performance('native', 'native_test.db', 'update')
test_performance('sqlcipher', 'sqlcipher_test.db', 'update')
3.2 实测结果与分析
跑出来的结果是这样的,原生SQLite插入1万条数据耗时大概0.3秒,SQLCipher插入耗时大概0.5秒,慢了0.2秒,大概是原生的1.6倍;查询的话,原生耗时0.1秒,SQLCipher耗时0.15秒,慢了50%;更新的话,原生耗时0.25秒,SQLCipher耗时0.4秒,慢了60%。
这个损耗其实是可以接受的,因为SQLite本身是轻量级数据库,加了加密之后,每次读写都要多做一步加解密的操作,所以会慢一点,但对于大部分小项目来说,这个速度差完全感知不到,比如做本地笔记APP,哪怕存10万条数据,打开也不会卡。
如果是SEE的话,实测结果和SQLCipher差不多,因为它的加密逻辑也是成熟的,性能损耗基本在同一个水平,没有明显的优势或者劣势。
3.3 性能损耗的影响因素
性能损耗不是固定的,还有几个因素会影响: 第一,加密密钥的长度:密钥越长,加解密的时间越长,但安全性越高,一般用128位的密钥就够了,兼顾安全和速度。 第二,操作的数据量:数据量越大,加解密的时间越长,比如插入100万条数据,SQLCipher的耗时可能是原生的2倍,但插入1000条的话,耗时差只有0.01秒,几乎可以忽略。 第三,数据库的大小:如果数据库文件超过1G,加了加密之后,查询速度会明显变慢,因为每次读取都要解密整个块,所以如果要存大量数据,不建议用SQLite加加密,还是用MySQL这类专业数据库。
四、应用场景与注意事项
4.1 适合的应用场景
不是所有项目都要给SQLite加加密,适合的场景主要有这几类: 第一,本地隐私数据存储:比如做笔记APP、密码管理工具,存用户的个人信息、密码,加了加密之后,哪怕用户把数据库文件拷走,也看不到内容。 第二,嵌入式设备存储:比如智能门锁、智能摄像头,设备里的数据库存了用户的指纹、监控记录,加了加密之后,就算设备被破解,数据也不会泄露。 第三,桌面工具的配置存储:比如一些付费软件的激活信息,加了加密之后,用户没法随便改激活信息,避免盗版。
4.2 使用时的注意事项
用加密扩展的时候,有几个坑一定要避开: 第一,密钥的存储:加密密钥绝对不能明文存在代码里,也不能存在数据库里,不然加了加密也白搭。比如做APP的话,密钥可以存在系统的密钥链里,比如Android的KeyStore,iOS的Keychain,只有程序自己能拿到。 第二,版本的兼容性:比如用SQLCipher的话,一定要用和你用的SQLite版本对应的SQLCipher版本,不然会出现数据库打不开的情况,比如SQLite 3.39.0对应的SQLCipher版本是4.5.0,用低版本的SQLCipher就会报错。 第三,数据库的备份:加了加密之后,备份数据库一定要用程序自带的备份功能,不能直接拷数据库文件,不然备份出来的文件也是加密的,打不开。比如SQLCipher有专门的备份函数,SEE也有对应的备份接口。 第四,性能优化:如果要提高速度,可以把频繁读写的小数据存在内存里,不用每次都读写数据库,减少加解密的次数;另外,尽量批量操作,比如批量插入、批量更新,减少数据库连接的次数,也能提高速度。
五、文章总结
给SQLite加加密,选扩展的时候,先看版权要求,商用选SQLCipher,个人用可以选SEE;然后看开发语言,Python、Java这类选SQLCipher,C/C++可以选wxSQLite3或者SEE。性能损耗方面,加了加密之后,速度会比原生慢50%到1倍,但对于大部分小项目来说,这个损耗完全可以接受,不会影响使用体验。
用的时候一定要注意密钥的存储,别把密钥明文写在代码里,不然加密就失去意义了;另外要注意版本的兼容性,避免出现数据库打不开的情况。总的来说,给SQLite加加密是一个很实用的功能,只要选对扩展,注意使用细节,就能兼顾安全和性能。
Comments