首页 Qt 带有加密功能的 SQLite Qt 插件(v1.0)

带有加密功能的 SQLite Qt 插件(v1.0)

18 3

QtCipherSqlitePlugin 最近升级到 1.0 版。这是一个比较大的升级,增加了一些新功能。感兴趣的童鞋可以升级试用下。

与之前的版本一样,QtCipherSqlitePlugin 还是基于 wxSQLite3 提供的 sqlite3secure 这个库。1.0 版的 QtCipherSqlitePlugin 插件将依赖的 wxSQLite3 升级到 4.0.4,sqlite 的版本是 3.24.0。

最新的 1.0 版代码可以使用 git 从 github 或者 gitee 获取:

git clone https://github.com/devbean/QtCipherSqlitePlugin.git
// OR
// git clone https://gitee.com/devbean/QtCipherSqlitePlugin.git
cd QtCipherSqlitePlugin
git checkout 1.0

全新的插件项目结构

前面几个版本的插件与 Qt 私有类紧密耦合,导致插件的代码会随着 Qt 的更新出现无法编译的情况。当初提到的解决方案是,将 Qt 的某些私有类的实现代码直接添加到插件代码树中,除去 Qt 私有类的依赖。这样,针对以后 Qt 的更新,插件只选择对性能有影响的部分进行跟进。这种实现固然能够减小插件代码的修改,但带来的问题是可能与未来的 Qt 版本不兼容,并且移植插件代码时可能会有一些问题。例如,虽然在外部使用时,插件的接口没有任何变化,但实际内部有了翻天覆地的变化。

为了解决这一问题,豆子重新调整了插件的项目结构。在 sqlitecipher 文件夹下的 sqlitecipher.pro 中增加了 Qt 私有文件的导入:

QT_FOR_CONFIG += sqldrivers-private
...
QT = core core-private sql-private

现在,我们的插件已经能够使用 Qt 私有类,而且编译插件也不需要做任何修改。目前豆子只使用 Qt 5.11 进行了测试,如果有其它版本的 Qt 不能正常使用,请及时联系豆子。

本次更新我们还是使用了 SQLITECIPHER 作为插件的名字。如果需要修改这个名字,Qt4 需要修改 smain.cpp 中的 DriverName 定义,Qt5 需要修改 SqliteCipherDriverPlugin.json 中的 SQLITECIPHER 一行。

编译插件

插件的编译可以通过 Qt Creator 或者直接使用命令。

使用 Qt Creator  编译,将 clone 下来的代码切换到 tag 1.0,然后打开整个项目,编译完毕之后将编译之后得到的 sqlitecipher(d).dll 复制到 Qt 的插件目录 plugins/sqldrivers 即可。

或者可以依次使用下面的命令:

git clone https://github.com/devbean/QtCipherSqlitePlugin.git
# OR https://gitee.com/devbean/QtCipherSqlitePlugin.git
cd QtCipherSqlitePlugin
git checkout 1.0
cd sqlitecipher
mkdir -p build && cd build qmake ../sqlitecipher.pro
make
make install # 可能需要提升权限

检查 QtCipherSqlitePlugin 是否成功加载

我们使用下面的代码检查 QtCipherSqlitePlugin 是否成功加载:

qDebug() << QSqlDatabase::drivers();

如果输出中有 SQLITECIPHER 的名字,说明插件是正常的。

为没有加密的数据库增加密码

Qt 默认提供的 SQLite 插件是没有加密功能的。新版本的 QtCipherSqlitePlugin 支持为原本没有加密的数据库增加密码,使用方法如下:

QSqlDatabase dbconn = QSqlDatabase::addDatabase("SQLITECIPHER");
dbconn.setDatabaseName("test.db");
dbconn.setPassword("test");
dbconn.setConnectOptions("QSQLITE_CREATE_KEY");
if (!dbconn.open()) {
    qDebug() << "Can not open connection: " << dbconn.lastError().driverText();
    exit(CONNECTION_FAILED);
}

上面的代码,我们使用 test.db 数据库,将密码设置为 test,同时指定连接选项为QSQLITE_CREATE_KEY。此时,调用open()函数之后,QtCipherSqlitePlugin 将使用改密码为这个数据库进行加密。

删除数据库密码

QtCipherSqlitePlugin 可以删除数据库密码,此时需要提供原密码,并使用连接选项QSQLITE_REMOVE_KEY,如下:

QSqlDatabase dbconn = QSqlDatabase::addDatabase("SQLITECIPHER");
dbconn.setDatabaseName("test.db");
dbconn.setPassword("test");
dbconn.setConnectOptions("QSQLITE_REMOVE_KEY");
if (!dbconn.open()) {
    qDebug() << "Can not open connection: " << dbconn.lastError().driverText();
    exit(CONNECTION_FAILED);
}

更新数据库密码

QtCipherSqlitePlugin 可以更新数据库原有密码,需要设置原密码,并且使用连接选项QSQLITE_UPDATE_KEY设置新密码,具体代码如下:

QSqlDatabase dbconn = QSqlDatabase::addDatabase("SQLITECIPHER");
dbconn.setDatabaseName("test.db");
dbconn.setPassword("test");
dbconn.setConnectOptions("QSQLITE_UPDATE_KEY=newtest");
if (!dbconn.open()) {
    qDebug() << "Can not open connection: " << dbconn.lastError().driverText();
    exit(CONNECTION_FAILED);
}

如果原密码不正确,QtCipherSqlitePlugin 会直接返回错误。

如果新密码设置为空,例如QSQLITE_UPDATE_KEY=,则作用等同于删除密码。

设置加密算法

QtCipherSqlitePlugin 支持四种加密算法:

  • AES 128 Bit CBC - No HMAC (wxSQLite3)
  • AES 256 Bit CBC - No HMAC (wxSQLite3)
  • ChaCha20 - Poly1305 HMAC (sqleet)
  • AES 256 Bit CBC - SHA1 HMAC (SQLCipher)

其中使用到的术语定义如下:

  • AES = Advanced Encryption Standard (Rijndael algorithm)
  • CBC = Cipher Block Chaining mode
  • HMAC = Hash Message Authentication Code
  • ChaCha20 = symmetric stream cipher developed by Daniel J. Bernstein
  • Poly1305 = cryptographic message authentication code (MAC) developed by Daniel J. Bernstein
  • SHA1 = Secure Hash Algorithm 1

默认加密算法在编译时设置。可以修改 sqlitecipher/sqlite3/sqlite3.pri 文件中的DEFINES += ...一行,找到CODEC_TYPE=CODEC_TYPE_CHACHA20一句,修改CODEC_TYPE的值即可。可选值为:

  • CODEC_TYPE_AES128
  • CODEC_TYPE_AES256
  • CODEC_TYPE_CHACHA20(默认)
  • CODEC_TYPE_SQLCIPHER

运行时修改加密算法,则可以通过连接参数QSQLITE_USE_CIPHER。例如下面的代码:

QSqlDatabase dbconn = QSqlDatabase::addDatabase("SQLITECIPHER");
dbconn.setDatabaseName("test.db");
dbconn.setPassword("test");
dbconn.setConnectOptions("QSQLITE_USE_CIPHER=sqlcipher");

QSQLITE_USE_CIPHER的可选值分别为:

  • aes128cbc
  • aes256cbc
  • chacha20
  • sqlcipher

使用连接参数还可以设置这些加密算法的详细参数值。

wxSQLite3: AES 128 Bit CBC - No HMAC

参数名默认值最小值最大值说明
AES128CBC_LEGACY001布尔类型,是否使用 LEGACY 模式

wxSQLite3: AES 256 Bit CBC - No HMAC

参数名默认值最小值最大值说明
AES256CBC_KDF_ITER40011 密钥导出函数(Key derivation function)的迭代次数
AES256CBC_LEGACY001布尔类型,是否使用 LEGACY 模式

sqleet: ChaCha20 - Poly1305 HMAC

参数名默认值sqleet最小值最大值说明
CHACHA20_KDF_ITER64007123451 密钥导出函数(Key derivation function)的迭代次数
CHACHA20_LEGACY0101布尔类型,是否使用 LEGACY 模式

SQLCipher: AES 256 Bit CBC - SHA1 HMAC

参数名默认值v3v2v1最小值最大值说明
SQLCIPHER_KDF_ITER6400064000400040001 密钥导出函数(Key derivation function)的迭代次数
SQLCIPHER_FAST_KDF_ITER22221 HMAC 密钥导出函数(HMAC Key derivation function)的迭代次数
SQLCIPHER_HMAC_USE111001布尔类型,是否使用 HMAC
SQLCIPHER_HMAC_PGNO111N/A02HMAC 存储页类型:0 = native, 1 = little endian, 2 = big endian
SQLCIPHER_HMAC_SALT_MASK0x3a0x3a0x3aN/A0255HMAC 盐的掩码字节
SQLCIPHER_LEGACY011101布尔类型,是否使用 LEGACY 模式

详细说明可以参考:https://github.com/devbean/QtCipherSqlitePlugin/wiki/Guide-for-1.0#ciphers

Legacy 模式

所有支持的加密算法都有一个 legacy 模式(遗留模式)。在这个模式下,数据库文件的头信息 16 ~ 23 字节也会被加密。这种行为与 SQLite Encryption Extension (SEE) 官方描述相悖。在官方描述中,数据库文件的 16 ~ 23 字节包含头信息,这个头信息不应该被加密。这一点很重要,因为这些字节会在加密扩展解密数据库头部时,被 SQLite 代码读取并解释。如果数据库头部的 16 ~ 23 字节被加密,SQLite 就不能正确确定数据库文件的页大小。因此,加密扩展或者用户就必须显式设置正确的页大小,否则 SQLite 就可能无法访问加密后的数据库。

从 wxSQLite3 3.1.0 开始(也就是 QtCipherSqlitePlugin 0.3+),wxSQLite3 本身提供的加密算法(AES 128 Bit 和 AES 256 Bit)可能会造成这一问题的代码才被修正。但是,并非所有之前的代码都会出现这个问题,出现问题的概率很低,大约是 1/8192。好消息是,使用新版本打开旧的数据库时,这一问题会被自动修正。但是,这种修正是单向的:修正之后的数据库就不能被旧的插件打开了。对于 sqleet (ChaCha20)和 SQLCipher 这两种加密算法,wxSQLite3 提供的加密结果本身会遵循 SQLite 的要求,但是,这会导致与使用 sqleet 以及 SQLCipher (Zetetic LLC)的原始方法加密而来的数据库不兼容。这是因为后两者的原始加密算法就没有提供 16 ~ 23 字节不加密的结果(未来版本可能会提供类似结果),因此,如果需要兼容使用二者原始算法加密而来的数据库,则需要设置 Lagacy 模式。

18 评论

深林孤鹰 2018年8月4日 - 13:40

豆子你好,0.7版的项目在Qt5.11.1的Android下可以编译,而1.0的却又很多错误不能通过编译...
错误摘抄如下:

D:\Documents\Downloads\Compressed\QtCipherSqlitePlugin-master\QtCipherSqlitePlugin-master\sqlitecipher\sqlite3\shathree.c:141: error: 'B2' undeclared (first use in this function)
B2 = ROL64((A22^D2), 43);
^

D:\Documents\Downloads\Compressed\QtCipherSqlitePlugin-master\QtCipherSqlitePlugin-master\sqlitecipher\sqlite3\shathree.c:154: error: lvalue required as left operand of assignment
B0 = ROL64((A03^D3), 28);
^

D:\Documents\Downloads\Compressed\QtCipherSqlitePlugin-master\QtCipherSqlitePlugin-master\sqlitecipher\sqlite3\fastpbkdf2.c:298: error: expected ';', ',' or ')' before 'ctx'
static inline void sha256_extract(sha256_ctx *restrict ctx, uint8_t *restrict out)
^

回复
豆子 2018年8月4日 - 17:58

试试在 sqlitecipher/sqlite3/sqlite4.pri 的DEFINES += 中添加一个定义:DEFINES += restrict=__restrict ...,然后再重新编译一下试试。这个错误是因为 gcc 使用的是 __restrict 关键字而 MSVC 用的是 restrict 关键字。改过之后看看还有没有问题?

回复
深林孤鹰 2018年10月13日 - 22:01

不好意思刚才看到回复...
按你的回复修改后,依然有些问题...
摘抄如下:
QtCipherSqlitePlugin-master\sqlitecipher\sqlite3\shathree.c:83: error: expected identifier or '(' before numeric constant
u64 B0, B1, B2, B3, B4;
^

U64没有被定义,我看了一下0.7版本,并没有shathree.c文件,内核好像变动比较大,所以不知所措了...
感谢百忙之中回复~

回复
457316336@qq.com 2019年6月14日 - 18:55

我也遇到相同的问题,报错信息一模一样,希望豆子大神百忙中抽空解决一下

回复
bluebuger 2019年8月21日 - 00:57

我这边 把u64 自己定义了。另外 B0 那一堆全部加了个前缀。编译过了,也生成了对应的So 。但是不知道怎么吧库 部署到安卓设备上

回复
明天会更好 2019年10月28日 - 12:12

Qt Creator中可以增加、删除、更新密码,读取数据。
换到vs2015+qt5.9.4环境下,QSqlDatabase::drivers()能打印出SQLITECIPHER但是db.open()一直失败,请楼主指点?

回复
豆子 2019年11月2日 - 16:34

这个我没有测试在 VS 开发的情况。使用 Qt Creator 也是使用的 VC 编译器吗?有什么错误提示吗?

回复
jimm 2020年6月14日 - 15:31

大佬,用这个加密后,是不是通用性打折扣?如何用这个加密后,可以用sqlite expert的密码插件可以打开数据库?我试 了里面给的几种加密方式,都无法用sqlite expert密码插件打开数据库,这样自己都不方便了查看这个文件了。

回复
豆子 2020年7月27日 - 22:14

能不能打开取决于是不是用相同算法加密。加密是为了保存某些敏感信息,比如密码等。

回复
ibex 2020年6月19日 - 11:35

大神,请问用这种加密方法,如何才能用sqlite expert 带加密插件的软件打开?我试了大佬插件的各种加密算法,都无法用sqlite expert这个软件打开。用c#自带的sqlite dll 加密数据库,是可以用sqlite expert带插件的版本。不然,数据库软件无法用专业的数据库软件查看编辑啊?

回复
豆子 2020年7月27日 - 22:16

这个要看 sqlite expert 用什么算法加密的,匹配的话才能使用 sqlite expert 查看,否则也是看不到的

回复
alex 2020年8月31日 - 16:53

请问一下,加密后,可以用database4这类型的软件 输入一个密码打开查看吗?我尝试了一下是不行,但是我想去这类型里面的软件去看看数据是否有误

回复
豆子 2020年9月2日 - 15:17

要注意选择的加密必须一致才可以

回复
lancelot 2020年10月22日 - 18:58

Navicat无法打开加密的文件,显示:26-file is not a database

回复
豆子 2020年10月31日 - 21:16

首先要知道 Navicat 使用的是哪种加密,然后看插件有没有提供相应的加密方式

回复
lancelot 2020年10月22日 - 19:05

CODEC_TYPE_AES128
CODEC_TYPE_AES256
CODEC_TYPE_CHACHA20(默认)
CODEC_TYPE_SQLCIPHER

这四种加密方式都试过了

回复
沈婷婷 2021年6月4日 - 17:54

为啥将直接编译好的dll文件给别人使用,项目崩溃
其他人得自己编译一份,才可以使用

回复
豆子 2021年6月19日 - 16:50

这个需要确定编译环境是一致的,不过具体原因还得仔细分析

回复

发表评论

关于我

devbean

devbean

豆子,生于山东,定居南京。毕业于山东大学软件工程专业。软件工程师,主要关注于 Qt、Angular 等界面技术。

主题 Salodad 由 PenciDesign 提供 | 静态文件存储由又拍云存储提供 | 苏ICP备13027999号-2