解码
使用控制台模式启动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.pngCell解析
读取出来的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字节大端序保存。
网友评论