SQLite 3.7.13的加密解密

2023-10-27

原文链接:

http://lancelot.blog.51cto.com/393579/940805

http://lancelot.blog.51cto.com/393579/940808

http://lancelot.blog.51cto.com/393579/940812

http://lancelot.blog.51cto.com/393579/940813

http://lancelot.blog.51cto.com/393579/940814

http://lancelot.blog.51cto.com/393579/940816

http://lancelot.blog.51cto.com/393579/940818


SQLite数据库支持加密和解密,但是免费版没有这个功能,不过网上已经有相关的资料,不过这些资料都不是基于SQLite 3.7.13版本的,这里根据网上找到的最全的资料进行整理,实现了SQLite 3.7.13版数据库的加密解密。本系列文章对此进行了详细说明。

开发环境:

操作系统

Win 7

IDE

Eclipse Juno (4.2) CDT

编译器

MinGW GCC 4.6.2

SQLite

3.7.13


首先要在sqlite3.c中最前面,添加代码(网上有说在sqlite3.h中添加也可,实际测试在sqlite3.h中打开该宏是无效的):

#ifndef SQLITE_HAS_CODEC

#define SQLITE_HAS_CODEC

#endif

 

这个宏是用来确定是否支持加密的。添加上述代码后编译,会出现如下错误:

D:\Research\MySQLite\Debug/../src/sqlite3.c:80963: undefined reference to `sqlite3CodecAttach'

D:\Research\MySQLite\Debug/../src/sqlite3.c:80968: undefined reference to `sqlite3CodecGetKey'

D:\Research\MySQLite\Debug/../src/sqlite3.c:80970: undefined reference to `sqlite3CodecAttach'

src\sqlite3.o: In function `sqlite3Pragma':

D:\Research\MySQLite\Debug/../src/sqlite3.c:94023: undefined reference to `sqlite3_key'

D:\Research\MySQLite\Debug/../src/sqlite3.c:94026: undefined reference to `sqlite3_rekey'

D:\Research\MySQLite\Debug/../src/sqlite3.c:94038: undefined reference to `sqlite3_key'

D:\Research\MySQLite\Debug/../src/sqlite3.c:94040: undefined reference to `sqlite3_rekey'

D:\Research\MySQLite\Debug/../src/sqlite3.c:94048: undefined reference to `sqlite3_activate_see'

src\sqlite3.o: In function `sqlite3RunVacuum':

D:\Research\MySQLite\Debug/../src/sqlite3.c:101744: undefined reference to `sqlite3CodecGetKey'


先不用管上面的编译错误,创建crypt.ccrypt.h,用来实现加密解密函数和相应接口的定义。

crypt.c里实现了加密解密函数,代码如下:

#include "crypt.h"

#include "memory.h"

 

/***********

 关键加密函数

 ***********/

int My_Encrypt_Func(unsigned char * pData, unsigned int data_len,

              unsigned char * key, unsigned int len_of_key)

 

{

       unsigned int i;

       unsigned char bit, val;

 

       for (i = 0; i < data_len; i++)

       {

              val = ~(*pData);

              *pData = val;

              pData++;

       }

       return 0;

}

 

/***********

 关键解密函数

 ***********/

int My_DeEncrypt_Func(unsigned char * pData, unsigned int data_len,

              unsigned char * key, unsigned int len_of_key)

 

{

       unsigned int i;

       unsigned char bit, val;

 

       for (i = 0; i < data_len; i++)

       {

              val = ~(*pData);

              *pData = val;

              pData++;

       }

       return 0;

}

这里加密解密函数就是简单的采用了求反的操作,目的是用来演示加密和解密。以后实际运用中把这两个函数内的算法修改为自己的加密解密算法即可。

注意:这里的加密解密函数实际上没有用到Key值,因此加密后使用任意Key值均可以解开数据库,但是加密后,用第三方工具是不能直接打开的。

 

crypt.h用来声明加密解密函数的定义,以便sqlite3.c包含加密解密接口,代码如下:

/**

 * 加密函数

 */

int My_Encrypt_Func(unsigned char * pData, unsigned int data_len,

              unsigned char * key, unsigned int len_of_key);

 

/**

 * 解密函数

 */

int My_DeEncrypt_Func(unsigned char * pData, unsigned int data_len,

              unsigned char * key, unsigned int len_of_key);

注意:网上的代码中,参数key定义为“const char *”类型,与sqlite3.c代码一起编译时会有错误,这里按照sqlite3.c中的类型修改为“unsigned char *”类型。


把crypt.c中实现的加密解密函数挂接到sqlite3.c中,并且实现前面编译提示的未实现的函数。在sqlite3.c的最后一行的后面,添加如下代码:
#ifdef SQLITE_HAS_CODEC
#include "crypt.h"
 
/***
 加密结构
 ***/
#define CRYPT_OFFSET 8
typedef struct _CryptBlock {
       BYTE* ReadKey; // 读数据库和写入事务的密钥
       BYTE* WriteKey; // 写入数据库的密钥
       int PageSize; // 页的大小
       BYTE* Data;
} CryptBlock, *LPCryptBlock;
 
#ifndef DB_KEY_LENGTH_BYTE          /*密钥长度*/
#define DB_KEY_LENGTH_BYTE    16   /*密钥长度*/
#endif
 
#ifndef DB_KEY_PADDING              /*密钥位数不足时补充的字符*/
#define DB_KEY_PADDING        0x33 /*密钥位数不足时补充的字符*/
#endif
 
/*** 下面是编译时提示缺少的函数 ***/
/** 这个函数不需要做任何处理,获取密钥的部分在下面 DeriveKey 函数里实现 **/
void sqlite3CodecGetKey(sqlite3* db, int nDB, void** Key, int* nKey) {
       return;
}
 
/*被 sqlite 和 sqlite3_key_interop 调用, 附加密钥到数据库.*/
int sqlite3CodecAttach(sqlite3 *db, int nDb, const void *pKey, int nKeyLen);
 
/**
 这个函数好像是 sqlite 3.3.17前不久才加的,以前版本的 sqlite里没有看到这个函数
 这个函数我还没有搞清楚是做什么的,它里面什么都不做直接返回,对加解密没有影响
 **/
void sqlite3_activate_see( const char* right) {
       return;
}
 
int sqlite3_key(sqlite3 *db, const void *pKey, int nKey);
int sqlite3_rekey(sqlite3 *db, const void *pKey, int nKey);
 
/***
 下面是上面的函数的辅助处理函数
 ***/
// 从用户提供的缓冲区中得到一个加密密钥
// 用户提供的密钥可能位数上满足不了要求,使用这个函数来完成密钥扩展
static unsigned char * DeriveKey( const void *pKey, int nKeyLen);
//创建或更新一个页的加密算法索引.此函数会申请缓冲区.

static LPCryptBlock CreateCryptBlock(unsigned char* hKey, Pager *pager,

              LPCryptBlock pExisting);
//加密/解密函数, 被pager调用

void * sqlite3Codec(void *pArg, unsigned char *data, Pgno nPageNum, int nMode);

//设置密码函数
int __stdcall sqlite3_key_interop(sqlite3 *db, const void *pKey, int nKeySize);
// 修改密码函数
int __stdcall sqlite3_rekey_interop(sqlite3 *db, const void *pKey, int nKeySize);
//销毁一个加密块及相关的缓冲区,密钥.
static void DestroyCryptBlock(LPCryptBlock pBlock);

static void * sqlite3pager_get_codecarg(Pager *pPager);

void sqlite3pager_set_codec( Pager *pPager,
              void *(*xCodec)( void*, void*, Pgno, int), void *pCodecArg);
 
//加密/解密函数, 被pager调用

void * sqlite3Codec(void *pArg, unsigned char *data, Pgno nPageNum, int nMode) {

       LPCryptBlock pBlock = (LPCryptBlock) pArg;
       unsigned int dwPageSize = 0;
 
       if (!pBlock)
              return data;
       // 确保pager的页长度和加密块的页长度相等.如果改变,就需要调整.
       if (nMode != 2) {

              PgHdr *pageHeader;

 

              pageHeader = DATA_TO_PGHDR(data);

              if (pageHeader->pPager->pageSize != pBlock->PageSize) {

                     CreateCryptBlock(0, pageHeader->pPager, pBlock);

              }
       }
 
       switch (nMode) {

       case 0: // Undo a "case 7" journal file encryption

       case 2: //重载一个页
       case 3: //载入一个页
              if (!pBlock->ReadKey)
                     break;
 

              dwPageSize = pBlock->PageSize;

 

              My_DeEncrypt_Func(data, dwPageSize, pBlock->ReadKey,

                           DB_KEY_LENGTH_BYTE); /*调用我的解密函数*/

 
              break;
 
       case 6: //加密一个主数据库文件的页
              if (!pBlock->WriteKey)
                     break;
 

              memcpy(pBlock->Data + CRYPT_OFFSET, data, pBlock->PageSize);

              data = pBlock->Data + CRYPT_OFFSET;

              dwPageSize = pBlock->PageSize;

 

              My_Encrypt_Func(data, dwPageSize, pBlock->WriteKey, DB_KEY_LENGTH_BYTE); /*调用我的加密函数*/

 
              break;
 
       case 7: //加密事务文件的页
              /*在正常环境下, 读密钥和写密钥相同. 当数据库是被重新加密的,读密钥和写密钥未必相同.
               回滚事务必要用数据库文件的原始密钥写入.因此,当一次回滚被写入,总是用数据库的读密钥,
               这是为了保证与读取原始数据的密钥相同.
               */
              if (!pBlock->ReadKey)
                     break;
 

              memcpy(pBlock->Data + CRYPT_OFFSET, data, pBlock->PageSize);

              data = pBlock->Data + CRYPT_OFFSET;

              dwPageSize = pBlock->PageSize;

 

              My_Encrypt_Func(data, dwPageSize, pBlock->ReadKey, DB_KEY_LENGTH_BYTE); /*调用我的加密函数*/

 
              break;
       }
 
       return data;
}
 
//销毁一个加密块及相关的缓冲区,密钥.
static void DestroyCryptBlock(LPCryptBlock pBlock)
{
       //销毁读密钥.
       if (pBlock->ReadKey) {
              sqliteFree(pBlock->ReadKey);
       }
 
//如果写密钥存在并且不等于读密钥,也销毁.
 

       if (pBlock->WriteKey && pBlock->WriteKey != pBlock->ReadKey) {

              sqliteFree(pBlock->WriteKey);
       }
 
       if (pBlock->Data) {
              sqliteFree(pBlock->Data);
       }
 
       //释放加密块.
       sqliteFree(pBlock);
}
 

static void * sqlite3pager_get_codecarg(Pager *pPager)

{

       return (pPager->xCodec) ? pPager->pCodecArg : NULL;

}
 
// 从用户提供的缓冲区中得到一个加密密钥
 
static unsigned char * DeriveKey( const void *pKey, int nKeyLen)
{
       unsigned char * hKey = NULL;
       int j;
 

       if (pKey == NULL || nKeyLen == 0)

       {
              return NULL;
       }
 

       hKey = sqliteMalloc(DB_KEY_LENGTH_BYTE + 1);

       if (hKey == NULL)
       {
              return NULL;
       }
 

       hKey[DB_KEY_LENGTH_BYTE] = 0;

 

       if (nKeyLen < DB_KEY_LENGTH_BYTE)

       {

              memcpy(hKey, pKey, nKeyLen); //先拷贝得到密钥前面的部分

              j = DB_KEY_LENGTH_BYTE - nKeyLen;

 
              //补充密钥后面的部分

              memset(hKey + nKeyLen, DB_KEY_PADDING, j);

       }
       else
       {
              //密钥位数已经足够,直接把密钥取过来

              memcpy(hKey, pKey, DB_KEY_LENGTH_BYTE);

       }
 
       return hKey;
}
 
//创建或更新一个页的加密算法索引.此函数会申请缓冲区.

static LPCryptBlock CreateCryptBlock(unsigned char* hKey, Pager *pager,

              LPCryptBlock pExisting)
 
{
       LPCryptBlock pBlock;
 
       if (!pExisting) //创建新加密块
       {

              pBlock = sqliteMalloc(sizeof(CryptBlock));

              memset(pBlock, 0, sizeof(CryptBlock));
              pBlock->ReadKey = hKey;
              pBlock->WriteKey = hKey;
              pBlock->PageSize = pager-> pageSize;
              pBlock->Data = ( unsigned char*) sqliteMalloc(
                           pBlock->PageSize + CRYPT_OFFSET);
       }
       else //更新存在的加密块
       {

              pBlock = pExisting;

 

              if (pBlock->PageSize != pager->pageSize && !pBlock->Data) {

                     sqliteFree(pBlock->Data);
                     pBlock->PageSize = pager-> pageSize;
 
                     pBlock->Data = ( unsigned char*) sqliteMalloc(
                                  pBlock->PageSize + CRYPT_OFFSET);
              }
       }
 
       memset(pBlock->Data, 0, pBlock->PageSize + CRYPT_OFFSET);
 
       return pBlock;
}
 
/*

 ** Set the codec for this pager

 */
void sqlite3pager_set_codec( Pager *pPager, void *(*xCodec)( void*, void*, Pgno, int), void *pCodecArg)
{

       pPager->xCodec = xCodec;

       pPager->pCodecArg = pCodecArg;

}
 
int sqlite3_key(sqlite3 *db, const void *pKey, int nKey)
{

       return sqlite3_key_interop(db, pKey, nKey);

}
 
int sqlite3_rekey(sqlite3 *db, const void *pKey, int nKey)
{

       return sqlite3_rekey_interop(db, pKey, nKey);

}
 
/*被 sqlite 和 sqlite3_key_interop 调用, 附加密钥到数据库.*/
int sqlite3CodecAttach(sqlite3 *db, int nDb, const void *pKey, int nKeyLen)
{
       int rc = SQLITE_ERROR;
       unsigned char* hKey = 0;
 
       //如果没有指定密匙,可能标识用了主数据库的加密或没加密.

       if (!pKey || !nKeyLen)

       {
              if (!nDb)
              {
                     return SQLITE_OK; //主数据库, 没有指定密钥所以没有加密.
              }
              else //附加数据库,使用主数据库的密钥.
              {
                     //获取主数据库的加密块并复制密钥给附加数据库使用

                     LPCryptBlock pBlock = (LPCryptBlock) sqlite3pager_get_codecarg(

                                  sqlite3BtreePager(db->aDb[0].pBt));
 
                     if (!pBlock)
                           return SQLITE_OK; //主数据库没有加密
 
                     if (!pBlock->ReadKey)
                           return SQLITE_OK; //没有加密
 
                     memcpy(pBlock->ReadKey, &hKey, 16);
              }
       }
       else //用户提供了密码,从中创建密钥.
       {

              hKey = DeriveKey(pKey, nKeyLen);

       }
 
       //创建一个新的加密块,并将解码器指向新的附加数据库.
       if (hKey)
       {

              LPCryptBlock pBlock = CreateCryptBlock(hKey,

                           sqlite3BtreePager(db->aDb[nDb].pBt), NULL);
 
              sqlite3pager_set_codec(sqlite3BtreePager(db->aDb[nDb].pBt),

                           sqlite3Codec, pBlock);

 
              rc = SQLITE_OK;
       }
 
       return rc;
}
 
// Changes the encryption key for an existing database.
int __stdcall sqlite3_rekey_interop(sqlite3 *db, const void *pKey, int nKeySize)
{

       Btree *pbt = db->aDb[0].pBt;

       Pager *p = sqlite3BtreePager(pbt);

       LPCryptBlock pBlock = (LPCryptBlock) sqlite3pager_get_codecarg(p);

       unsigned char * hKey = DeriveKey(pKey, nKeySize);

       int rc = SQLITE_ERROR;
 

       if (!pBlock && !hKey)

              return SQLITE_OK;
 
       //重新加密一个数据库,改变pager的写密钥, 读密钥依旧保留.
       if (!pBlock) //加密一个未加密的数据库
       {

              pBlock = CreateCryptBlock(hKey, p, NULL);

              pBlock->ReadKey = 0; // 原始数据库未加密

              sqlite3pager_set_codec(sqlite3BtreePager(pbt), sqlite3Codec, pBlock);

       }
       else // 改变已加密数据库的写密钥
       {
              pBlock->WriteKey = hKey;
       }
 
       // 开始一个事务

       rc = sqlite3BtreeBeginTrans(pbt, 1);

       if (!rc)
       {
              // 用新密钥重写所有的页到数据库。

              Pgno nPage = sqlite3PagerPagecount(p);

              Pgno nSkip = PAGER_MJ_PGNO(p);

              void *pPage;

              Pgno n;

 

              for (n = 1; rc == SQLITE_OK && n <= nPage; n++)

              {
                     if (n == nSkip)
                           continue;
 
                     rc = sqlite3PagerGet(p, n, &pPage);
                     if (!rc)
                     {

                           rc = sqlite3PagerWrite(pPage);

                           sqlite3PagerUnref(pPage);
                     }
              }
       }
 
       // 如果成功,提交事务。
       if (!rc)
       {

              rc = sqlite3BtreeCommit(pbt);

       }
 
       // 如果失败,回滚。
       if (rc)
       {
              sqlite3BtreeRollback(pbt);
       }
 
       // 如果成功,销毁先前的读密钥。并使读密钥等于当前的写密钥。
       if (!rc)
       {
              if (pBlock->ReadKey)
              {
                     sqliteFree(pBlock->ReadKey);
              }
 
              pBlock->ReadKey = pBlock->WriteKey;
       }
       else // 如果失败,销毁当前的写密钥,并恢复为当前的读密钥。
       {
              if (pBlock->WriteKey)
              {
                     sqliteFree(pBlock->WriteKey);
              }
              pBlock->WriteKey = pBlock->ReadKey;
       }
 
       // 如果读密钥和写密钥皆为空,就不需要再对页进行编解码。
       // 销毁加密块并移除页的编解码器

       if (!pBlock->ReadKey && !pBlock->WriteKey)

       {

              sqlite3pager_set_codec(p, NULL, NULL);

              DestroyCryptBlock(pBlock);
       }
 
       return rc;
}
 
/***
 下面是加密函数的主体
 ***/
int __stdcall sqlite3_key_interop(sqlite3 *db, const void *pKey, int nKeySize)
{

       return sqlite3CodecAttach(db, 0, pKey, nKeySize);

}
 
// 释放与一个页相关的加密块
void sqlite3pager_free_codecarg( void *pArg)
{
       if (pArg)
              DestroyCryptBlock((LPCryptBlock) pArg);
}
 
#endif //# ifdef SQLITE_HAS_CODEC
 
特别说明:
DeriveKey 函数,这个函数是对密钥的扩展。比如,你要求密钥是128位,即是16字节,但是如果用户只输入 1个字节呢?2个字节呢?或输入50个字节呢?你得对密钥进行扩展,使之符合16字节的要求。

DeriveKey 函数就是做这个扩展的。有人把接收到的密钥求md5,这也是一个办法,因为md5运算结果固定16字节,不论你有多少字符,最后就是16字节。这是md5算法的特点。但是我不想用md5,因为还得为它添加包含一些 md5 的.c或.cpp文件。我不想这么做。我自己写了一个算法来扩展密钥,很简单的算法。当然,你也可以使用你的扩展方法,也而可以使用 md5 算法。只要修改 DeriveKey 函数就可以了。

在 DeriveKey 函数里,只管申请空间构造所需要的密钥,不需要释放,因为在另一个函数里有释放过程,而那个函数会在数据库关闭时被调用。参考上面 DeriveKey 函数来申请内存。

上面的代码是从网上下载下来的,它使用的SQLite版本比较旧,因此在SQLite 3.7.13下编译不通过,下面需要对编译错误和警告逐一修正。

编译信息

原因与修改方法

'Pager' has no member named 'pCodecArg'

3.7.13版本中,Pager的成员变量pCodecArg名称修改为pCodec,因此用到pCodecArg变量的地方修改为使用pCodec

too few arguments to function 'sqlite3PagerPagecount'

原来sqlite3PagerPagecount()函数用返回值得到页数量,3.7.13改为用指针参数得到页数量。

修改前代码:

Pgno nPage = sqlite3PagerPagecount(p);

修改如下:

int nPage;

sqlite3PagerPagecount(p, &nPage);

too few arguments to function 'sqlite3BtreeRollback'

3.7.13版中sqlite3BtreeRollback()函数增加了个参数,是表示之前SQL语句执行结果的,在网上查了一下,这里直接传常量SQLITE_OK

implicit declaration of function 'sqliteFree'

sqliteFree()函数在3.7.13版本中已经没有了,修改为使用sqlite3_free()函数。

implicit declaration of function 'sqliteMalloc'

原因同上,修改为使用sqlite3_malloc()函数。

 

 

implicit declaration of function 'DATA_TO_PGHDR'

3.7.13版本中,宏DATA_TO_PGHDR已经被去掉,这里暂时把该if语句下的代码全部注释。

warning: passing argument 2 of 'sqlite3pager_set_codec' from incompatible pointer type

sqlite3pager_set_coedc()函数的第二个参数类型为:void *(*xCodec)(void*, void*, Pgno, int)

而调用的地方传递的参数类型为:void * sqlite3Codec(void *pArg, unsigned char *data, Pgno nPageNum, int nMode)

很明显,第二个参数类型不匹配,修改sqlite3Codec()函数的第二个参数类型为void *,注意相应的函数声明的地方也要修改。

warning: passing argument 3 of 'sqlite3PagerAcquire' from incompatible pointer type

这里第三个参数类型为void *,实际要求的数据类型为DbPage *,将对应变量类型定义为DbPage *即可。

warning: variable 'bRc' set but not used

这个警告不影响使用,不用改。

warning: 'sqlite3PagerSetCodec' defined but not used

同上

采用上一节的方法为SQLite添加了加密解密功能后,使用方法如下:

1、 在调用sqlite3_open()函数打开数据库后,要调用sqlite3_key()函数为数据库设置密码;

2、 如果数据库之前有密码,则调用sqlite3_key()函数设置正确密码才能正常工作;

3、 如果一个数据库之前没有密码,且已经有数据,则不能再为其设置密码;

4、 如果要修改密码,则需要在第一步操作后,调用sqlite3_rekey()函数设置新的密码;

5、 设置了密码的SQLite数据库,无法使用第三方工具打开;

 

具体使用的示例代码如下:

#include <stdio.h>

#include <stdlib.h>

#include "sqlite3.h"

#define  SQLITE3_STATIC

 

extern int sqlite3_key(sqlite3 *db, const void *pKey, int nKey);

 

static int _callback_exec(void * notused,int argc, char ** argv, char ** aszColName)

{

    int i;

    for ( i=0; i<argc; i++ )

    {

        printf( "%s = %s\r\n", aszColName[i], argv[i] == 0 ? "NUL" : argv[i] );

    }

 

    return 0;

}

 

int main(int argc, char * argv[])

{

    const char * sSQL;

    char * pErrMsg = 0;

    int ret = 0;

    sqlite3 * db = 0;

 

    //创建数据库

    ret = sqlite3_open("d:\\encrypt.db", &db);

 

    //添加密码

    ret = sqlite3_key( db, "dcg", 3 );

 

    //在内存数据库中创建表

    sSQL = "create table class(name varchar(20), student);";

    sqlite3_exec( db, sSQL, _callback_exec, 0, &pErrMsg );

 

    //插入数据

    sSQL = "insert into class values('mem_52911', 'zhaoyun');";

    sqlite3_exec( db, sSQL, _callback_exec, 0, &pErrMsg );

 

    //取得数据并显示

    sSQL = "select * from class;";

    sqlite3_exec( db, sSQL, _callback_exec, 0, &pErrMsg );

 

    //关闭数据库

    sqlite3_close(db);

    db = 0;

 

    return 0;

}

现象与原因

采用上面的方法对数据库进行加密,存在页面尺寸错乱的问题。在SQLiteDB文件中,第1617两个字节的值表示数据库中每个页的大小,SQLite规定页大小必须是512的倍数,如果加密算法恰好导致这两个字节的值为512的倍数,且与数据库的实际页面大小不一样,就会导致不能进行数据库操作。

其原因是在sqlite3_open()函数中,会读取DB文件头,从1617字节得到页大小,但是sqlite3_open()函数中没有调用解密函数,因此得到的就是错误的值。一般来说,采用的加密算法不会导致1617这两个字节恰好是512的倍数,在SQLite内部有保护,如果这个数据不是512的倍数,或者超过一定数值,则自动取默认值1024

但是也不排除加密算法没有选择好,导致这两个字节的值出现问题,譬如我曾经采用个简单的加密算法,就是将每个字节循环左移一位,结果1617这两个字节的值就是2048,正好是512的倍数,最终导致程序崩溃。

解决方案

这种问题,实际上是因为sqlite代码中没有充分考虑这种pagesize在加密后读取的问题,解决方法有两个:

方案一:(彻底解决方案)修改sqlite源码,使opendatase读取的pagesize无效,在设置好数据库密钥以后,第一次读取数据时重新计算pagesize

方案二:(针对加密的修补方案)修改sqlite3_key的加密实现,在设置密钥时,解密读取数据库的头信息,读取解密后的pagesize,再把这个正确的pagesize设置回去;




本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SQLite 3.7.13的加密解密 的相关文章

  • Pytorch中contiguous()函数理解

    引言 在pytorch中 只有很少几个操作是不改变tensor的内容本身 而只是重新定义下标与元素的对应关系的 换句话说 这种操作不进行数据拷贝和数据的改变 变的是元数据 会改变元数据的操作是 narrow view expand tran
  • oracle 查出一个表中字段值出现次数大于2的所有记录

    表web order 列 name businesscode a account 周桥 18929609222 3754031157710000妙药 18929609233 3754031157712344灵丹 18929609189 37
  • V2X车联网-学习整理笔记

    一致性测试预备条件 1 具备WIFI或者LAN通信能力 2 具备GNSS能力 能够获取设备经纬度以及授时 3 具备RF通信能力 aid为应用标识 应用标识分配如下 111 普通车辆状态 112 普通车辆关键事件提醒 113 紧急车辆状态 1
  • 服务器维护 文档,ERP系统维护服务器维护管理文档.docx

    文档介绍 ERP系统机箱及办事器治理维护文档 作者 数据技能组 创建日期 2013 05 08 修他日期 版本 1 0 目录 目录 2 编写说明 3 使用东西 4 参考文档 4 图标说明 4 治理维护界面详细说明 5 治理维护界面的进入 6
  • gedit注释快捷建 ctrl+m

    sudo apt get install gedit plugins 安装gedit 插件 终端输入gedit命令 打开gedit 最后把code comment勾上 重启机器就可以使用Ctrl M注释了 Ctrl N 新开一个窗口新建文档
  • PyFlink使用说明:建表及连接Mysql数据库

    PyFlink1 16 0 使用说明 建表及连接Mysql数据库 引言 安装运行环境 PyFlink创建作业环境 一 创建一个 Table API 批处理表环境 二 创建一个 Table API 流处理表环境 三 创建一个 DataStre
  • css3 文本超出容器后显示...以及超出几行后显示...

    前言 好记性不如烂笔头 记录一下自己常用的css样式 一 文本超出容器后显示 div class ellipsis main 国际酒店政策国际酒店政策国际酒店政策国际酒店政策国际酒店政策 div ellipsis main width 10
  • error: The following untracked working tree files would be overwritten by checkout:

    1 可以使用 git status 查看什么情况 2 原因可能是这些变化没有提交 根据git status 的提醒 可以提交 然后就可以切换分支了 3 git checkout 你想切换的分支
  • 电商平台怎么搭建

    越来越多商家致力于搭建并运营自己的私域电商平台 大家都清楚了解拥有自己电商平台的好处 有利于品牌的塑造与提升 提高品牌曝光度和认知度 提高客户黏性 降低渠道成本 乔拓云平台模板式搭建电商平台 方法简单实用 适合电脑零基础的朋友自己搭建 通过
  • rv1126如何切换720p和1080p

    切换720p和1080p可以使用modetest 但是需要将这两种模式都添加到connector中去 添加一个新的mode到connector中去 其实内核中已经有相关接口了 需要做一些小改动 1 不采用设备树的方式去配置 将720p和10
  • 聚水潭无需API开发连接伙伴云,实现新增订单信息自动同步到表单汇总

    聚水潭用户使用场景 电商行业通常使用聚水潭作为企业的ERP系统 然而 每当聚水潭产生新订单时 企业人员常常需要将订单信息手动复制并录入到伙伴云存储 汇总 包括订单单号 状态 金额等20多项信息 这种人工手动复制和录入的方式容易导致订单数据出
  • C++知识框架梳理

    封装 继承 多态被称为面向对象的三大法宝 一 封装 1 类 a 如何创建自己的类 形式如下 class student 类名 student string name 类里面两个内容 年龄名字 叫做类的成员数据 也叫作属性 int age 早
  • Android码农是如何进入腾讯的,Flutter全方位深入探索

    正式加入字节跳动 分享一点面试小经验 今天正式入职了字节跳动 工号超吉利 尾数是3个6 然后办公环境也很好 这边一栋楼都是办公区域 公司内部配备各种小零食 饮料 还有免费的咖啡 15楼还有健身房 而且公司包三餐来着 下午三点半左右还会有阿姨
  • STM32关于定时器输出多路PWM波的持续跟进

    简介 这里简单用stm32产生多路PWM 1 32的通用定时器3可以产生4路PWM输出 同频率 不同占空比 2 一个定时器产生的PWM频率由定时器输入频率决定 时钟树决定通用定时器时钟来自APB1 且如果APB1的分频为1的话 定时器时钟为
  • 抽象类 接口

    1 抽象类 public abstract class AbstractClass 里面至少有一个抽象方法 public int t 普通数据成员 public abstract void method1 抽象方法 抽象类的子类在类中必须实
  • 功率时延谱(PDP)与三种选择性衰落

    衰落与弥散是无线信道的基本特性 电磁波经过无线信道传输后会使原本的信号在时域 频域 空域 角度 上产生弥散现象 导致波形在时间 频谱 空间上产生交叠 引起信号的失真 多径效应在时域上引起信号时延扩展 在频域上定义了相关带宽指标 当信号带宽大
  • k8s调度 原理_深入剖析k8s之默认调度器调度策略解析

    本篇专注在调度过程中 Predicates 和 Priorities 这两个调度策略主要发生作用的阶段 Predicates 首先 我们一起看看 Predicates Predicates 在调度过程中的作用 可以理解为 Filter 即
  • 如何使用nfsiostat来分析nfs存储性能问题

    nfsiostat介绍 Sysstat家族包括一个名叫nfsiostat的实用程序 它和iostat有诸多类似之处 它允许你监控NFS文件系统上的读写情况 其用法也和iostat类似 最基本的命令用法是跟上几个参数和两个数字 这两个数字分别
  • vue父子组件传值,父组件内容更新子组件内容不实时更新

    背景 vue父子组件传值不能实时更新问题 父组件将值传给了子组件 但子组件显示的值还是原来的初始值 并没有实时更新 总结了以下三种情况及解决方案 1 子组件没有正确监听父组件传递的值 在子组件中 确保正确地声明了props 并且监听了父组件

随机推荐

  • CVPR2023论文汇总

    点击下方卡片 关注 自动驾驶之心 公众号 ADAS巨卷干货 即可获取 点击进入 自动驾驶之心 全栈算法 技术交流群 CVPR2023中稿paper已经陆续放出来了 自动驾驶之心团队为大家整理了计算机视觉 BEV 分割 Occpuancy v
  • Vue的插槽与作用域插槽详解

    在Vue中 插槽 Slot 是一个非常强大且灵活的特性 用于在父组件中定义子组件的内容 Vue提供了两种主要类型的插槽 默认插槽 Slot 和作用域插槽 Scoped Slot 本篇博文将深入介绍这两种插槽类型 从基础到进阶 默认插槽 Sl
  • 打开一个php网页出现2个ip,php根据ip地址查地区

    自己以前做过一个程序 根据discuz里面的ip查询改的 ip地址所属地区计算 修改自 discuz 使用dicuz tinyipdata数据文件 将一些英文提示修改为汉字 is simple true的话显示到市 false显示到网通电信
  • maven项目中添加MySql依赖失败(以及maven的安装到maven项目的使用过程)

    maven项目中添加MySql依赖失败 以及maven的安装到maven项目的使用过程 1 maven项目中添加MySql依赖失败 报错信息 Dependency mysql mysql connector java not found 解
  • Cadence Allegro PCB设计88问解析(三十) 之 Allegro中 PCB的3D模型导出

    一个学习信号完整性仿真的layout工程师 在进行PCB投板之前 往往需要将PCB的结构发给结构的同事确认 一般会导出DXF和EMN文件 或者导出3D模型 3D模型包含版型 器件的实际3D模型等等 可以比较直观的看到PCB板上的器件情况 下
  • 【git 错误】git 使用中的问题汇总 不定期更新 git报错 Please enter a commit message to explain

    1 Please enter a commit message to explain why this merge is necessary 解决办法 1 按键盘字母 i 进入insert模式 2 修改最上面那行黄色合并信息 可以不修改 3
  • ImportError: /usr/lib/x86_64-linux-gnu/libstdc++.so.6: version `GLIBCXX_3.4.26‘ not foun Python GDAL

    前言 更新完pytorch1 9 0之后 突然GDAL包不能用了 但是代码调试的时候是正常的 本文给出具体的解决过程 提示一下 其实这种因为软件更新导致某个动态库不能通用的情况 一般的解决方法 就是在本机上查找一下有没有别的地方有 这样的解
  • ADAS先进驾驶辅助系统(Advanced Driver Assistant System)

    先进驾驶辅助系统 Advanced Driver AssistantSystem 简称ADAS 是利用安装于车上的各式各样的传感器 可侦测光 热 压力等变数 在第一时间收集车内外的环境数据 进行静 动态物体的辨识 侦测与追踪等技术上的处理
  • YOLOv7——目标检测数据集划分篇

    法一 1 准备VOC数据集 将所有数据集图片放入JPEGImages文件夹中 所有的图片对应的xml文件放入Annotations中 ImageSets文件夹中创建Main文件夹 暂时Main文件夹为空 文件夹结构 datasets Ann
  • 数据标准详细概述-2022

    1 数据标准的是什么 在实际的工作生产中 我们一般会参照国家标准 地方标准 行业标准等来进行具体的活动 来确保我们生成过程符合监管要求 便于上下游协同等 于是我们会见到如下的标准指导文件 同样 数据标准也会以文件的形式存在 在除了国标 行标
  • qq人脸更换_QQ安全中心现在怎么替换人脸设置或删除人脸?

    以下内容收集自网络 题主可以参考一下 1 我们从手机中打开QQ安全中心 如果还不是最新版本的话 请先升级到最新版本 2 在QQ安全中心首页 点击最下方的 工具 按钮 3 在 工具 页面 点击打开 实验室 这个图标 4 在打开的界面 点击打开
  • 四阶魔方玩法总结V1.0

    四阶魔方玩法总结V1 0 1 引言 今写此文 我主要是为了方便自己再次玩其魔方的时候 可以快速的想起 避免又从头学起 毕竟自己学会的 理解的 写出来的东西 再次玩魔方的时候 仅仅是回顾和追忆的过程 不存在学习 理解和消化的过程 避免再次浪费
  • 12.大数据之Hive性能优化

    hive性能调优 1 HADOOP计算框架特性 数据量大不是问题 数据倾斜是个问题 jobs数比较多的作业运行效率相对比较低 比如即使有几百行的表 如果多次关联多次汇总 产生十几个jobs 耗时很长 原因是map reduce作业初始化的时
  • 二叉树类型的常考选择题知识储备(二叉树的性质)

    1 若规定 根节点的层数为 1 则 一个非空二叉树的 第 i 层 上最多有 2i 1 i gt 0 个结点 2 若规定只有根节点的二叉树的深度为1 则 深度为 K 的二叉树的 最大结点数是 2k 1 k gt 0 3 对于任意一个二叉树 如
  • Angular管道操作符(

    一 模板表达式操作符 模板表达式语言使用了JavaScript 语法的子集 并补充了几个用于特定场景的特殊操作符 管道操作符 安全导航操作符 二 管道操作符 在绑定之前 表达式的结果可能需要一些转换 例如 可能希望吧数字显示成金额 强制文本
  • 【Web3】Mnemonic Word Create Wallet

    目录 Create Mnemonic Word 介绍 一 根据 Mnemonic Word 生成密钥对 keypair 二 通过 keypair 获取 Wallet 地址 和 private key 代码 Create Mnemonic W
  • bash: ./make.sh: /bin/sh^M: 解释器错误: 没有那个文件或目录

    原因 在Linux运行 sh文件时报上述错误 原因是因为该文件在windows系统上打开过 关闭后其中的空格符号和Linux的不同 导致这个报错 可以通过sed命令与正则的配合将文件中的空格符号替换成linux的空格 解决方法 sed i
  • 实验6

    一 MPEG 1 Audio LayerII编码器原理 1 多相滤波器组 将PCM样本变换到32个子带的频域信号 如果输入的采样频率为48kHz 那么子带的频率宽度为48 2 32 0 75Hz 缺点 1 等带宽的滤波器组与人类听觉系统的临
  • MSCOCO数据标注详解(超详细)

    博主大大 风吴痕 对MSCOCO的讲解 超级详细而且有代码举例 转发收藏 后面方便自己查看 https blog csdn net wc781708249 article details 79603522
  • SQLite 3.7.13的加密解密

    原文链接 http lancelot blog 51cto com 393579 940805 http lancelot blog 51cto com 393579 940808 http lancelot blog 51cto com