sqlcipher代码阅读

解码

使用控制台模式启动sqlcipher并打开一个加密的db文件后,使用以下指令进行测试。

sqlcipher ./sqlcipher.db
# 下面是直接在sqlcipher的控制台中输入指令:
PRAGMA key ='936329a';
PRAGMA cipher_kdf_algorithm = PBKDF2_HMAC_SHA1;
PRAGMA cipher_hmac_algorithm = HMAC_SHA1;
PRAGMA cipher_use_hmac = OFF;
PRAGMA cipher_page_size = 1024;
PRAGMA kdf_iter = 4000;
.databases

在.database指令执行时,触发了打开数据库并解密的动作。

image.png

上图可见,由 process_input 处理指令,最终执行到static int readDbPage(PgHdr *pPg){(page.c),在此函数中,先使用sqlite3OsRead从db文件中读取一页 或 使用sqlite3WalReadFrame从wal文件中读取缓存页。

拿到数据后使用了这么一行代码:CODEC1(pPager, pPg->pData, pPg->pgno, 3, rc = SQLITE_NOMEM_BKPT);来对读取到内存中的数据进行处理,CODEC1是个宏定义,在有SQLITE_HAS_CODEC宏的情况下(也就是编码为sqlcipher,如果没有宏则只有sqlite的功能)会调用 pPager中的xCodec成员(函数指针),此函数指针对应函数static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode)(crypto.c)。

sqlite3Codec函数中调用openssl对这块内存进行解密。

数据解析

由page.c读取出来的数据是对应sqlite的物理页(页内有cell)结构,在上层由btree.c定义的MemPage、Btree、CellInfo等相关数据结构来管理。

通过static int lockBtree(BtShared *pBt)函数来获取第一页,同时起到锁定整个数据表的功能。

然后对sqlite的文件头进行格式验证后,如果第19个字节是2(File format read version. 1 for legacy; 2 for WAL.)则使用sqlite3PagerOpenWal,再调用pagerOpenWal来打开日志文件。

最终wal日志文件由wal.c中的sqlite3WalOpen函数来创建或打开,内部使用sqlite3OsOpen来创建wal文件。

其它页面的读取由Btree的结点遍历来触发,如下图堆栈:

image.png

Cell解析

读取出来的Page结构MemPage有一个成员函数xParseCell,根据page的类型不同,在初使化时设置为了不同的函数:

  • btreeParseCellPtr() => table btree leaf nodes
  • btreeParseCellNoPayload() => table btree internal nodes
  • btreeParseCellPtrIndex() => index btree nodes

每个Cell根据页类型不同,cell的结构也不一样,详见:https://www.sqlite.org/fileformat2.html#b_tree_pages

image.png

可见,除了类型为5的b树的Cell,其它 Cell都可能带有数据(PlayLoad),但每种cell所能包含的数据是有大小限制,超出这个大小的数据就要存放在其它页中了。这种页的前4个字节是个大端序的数字,表明有效数据的大小。页号由cell尾部的4字节大端序保存。

你可能感兴趣的:(sqlcipher代码阅读)