Sqleet

Свободный проект SQLeet для шифрации базы данных SQLite3.

Поставляется в виде самого файла базы данных sqlite3.c (у версии 3.31.1 это 2020-01-27) и файла шифрации sqleet.c.

Тут идет прямое обращение к функциям sqlite типа sqlite_open().

Но мы хотим использовать sqleet с Qt 4.8.1 например. А там sqlite версии 3.7.7.1 ( это 2011-06-28). Вот такие мы идиоты.

Поэтому тупая замена в Qt файла sqlite3.c дает нам ошибки при сборке.

Что надо уяснить. Проект sqleet.c маленький (665 строк). 

Почему нельзя взять целиком файл базы данных расшифровать его функционалом openssl в памяти и подсунуть это в sqlite.

Sqleet проект содержит еще файл shell.c, который уже совсем не маленький (19000 строк кода), где есть функции wmain(), main(), open_db(). все понятно это готовый пример. 

Раздефайниваем main(), убираем из нашего проекта main.c (созданный по умолчанию) и вперед,все собирается нормально, влетаем в отладку, идем через множество вызываемых функций и получаем консольное окно:

фотка 1

Или удобнее в текстовом виде

.auth ON|OFF             Show authorizer callbacks
.backup ?DB? FILE        Backup DB (default "main") to FILE
.bail on|off             Stop after hitting an error.  Default OFF
.binary on|off           Turn binary output on or off.  Default OFF
.cd DIRECTORY            Change the working directory to DIRECTORY
.changes on|off          Show number of rows changed by SQL
.check GLOB              Fail if output since .testcase does not match
.clone NEWDB             Clone data into NEWDB from the existing database
.databases               List names and files of attached databases
.dbconfig ?op? ?val?     List or change sqlite3_db_config() options
.dbinfo ?DB?             Show status information about the database
.dump ?TABLE? ...        Render all database content as SQL
.echo on|off             Turn command echo on or off
.eqp on|off|full|...     Enable or disable automatic EXPLAIN QUERY PLAN
.excel                   Display the output of next command in spreadsheet
.exit ?CODE?             Exit this program with return-code CODE
.expert                  EXPERIMENTAL. Suggest indexes for queries
.explain ?on|off|auto?   Change the EXPLAIN formatting mode.  Default: auto
.filectrl CMD ...        Run various sqlite3_file_control() operations
.fullschema ?--indent?   Show schema and the content of sqlite_stat tables
.headers on|off          Turn display of headers on or off
.help ?-all? ?PATTERN?   Show help text for PATTERN
.import FILE TABLE       Import data from FILE into TABLE
.imposter INDEX TABLE    Create imposter table TABLE on index INDEX
.indexes ?TABLE?         Show names of indexes
.limit ?LIMIT? ?VAL?     Display or change the value of an SQLITE_LIMIT
.lint OPTIONS            Report potential schema issues.
.load FILE ?ENTRY?       Load an extension library
.log FILE|off            Turn logging on or off.  FILE can be stderr/stdout
.mode MODE ?TABLE?       Set output mode
.nullvalue STRING        Use STRING in place of NULL values
.once (-e|-x|FILE)       Output for the next SQL command only to FILE
.open ?OPTIONS? ?FILE?   Close existing database and reopen FILE
.output ?FILE?           Send output to FILE or stdout if FILE is omitted
.parameter CMD ...       Manage SQL parameter bindings
.print STRING...         Print literal STRING
.progress N              Invoke progress handler after every N opcodes
.prompt MAIN CONTINUE    Replace the standard prompts
.quit                    Exit this program
.read FILE               Read input from FILE
.restore ?DB? FILE       Restore content of DB (default "main") from FILE
.save FILE               Write in-memory database into FILE
.scanstats on|off        Turn sqlite3_stmt_scanstatus() metrics on or off
.schema ?PATTERN?        Show the CREATE statements matching PATTERN
.selftest ?OPTIONS?      Run tests defined in the SELFTEST table
.separator COL ?ROW?     Change the column and row separators
.sha3sum ...             Compute a SHA3 hash of database content
.shell CMD ARGS...       Run CMD ARGS... in a system shell
.show                    Show the current values for various settings
.stats ?on|off?          Show stats or turn stats on or off
.system CMD ARGS...      Run CMD ARGS... in a system shell
.tables ?TABLE?          List names of tables matching LIKE pattern TABLE
.testcase NAME           Begin redirecting output to 'testcase-out.txt'
.testctrl CMD ...        Run various sqlite3_test_control() operations
.timeout MS              Try opening locked tables for MS milliseconds
.timer on|off            Turn SQL timer on or off
.trace ?OPTIONS?         Output each SQL statement as it is run
.vfsinfo ?AUX?           Information about the top-level VFS
.vfslist                 List all available VFSes
.vfsname ?AUX?           Print the name of the VFS stack
.width NUM1 NUM2 ...     Set column widths for "column" mode

То есть это консольная утилита для ввода команд вручную. На картинке ввели .help .

В которой есть информация, что можно в параметре вызова указать имя файла базы данных.

Путь по main функции в кратце:

int SQLITE_CDECL main(int argc, char **argv){
ShellState data;
sqlite3_initialize();
sqlite3_appendvfs_init(0,0,0);
....
if( access(data.zDbFilename, 0)==0 ){
	open_db(&data, 0);
}
..
process_sqliterc(&data,zInitFile);
...
rc = process_input(&data);

Тут отличный пример открытия и инициализации базы данных sqlite и в конце ожидание ввода консольной команды.

Мы в своей любимой среде Qt Creator настраиваем передачу аргумента (имя файла нашей тестовой базы данных) при запуске программы:

фотка 2

Вводим команду .dump и видим, что (поскольку наша база данных aaa.db не пустая) все ее содержание

.dump
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE suppliers (id integer primary key autoincrement,name varchar(100),agent int default -1,supplierName varchar(256),supplierINN varchar(12),supplierPhone varchar(19),transfName varchar(64),transfINN varchar(12),transfAddress varchar(256),transfPhone varchar(19),operatorPhone varchar(19),payingOp varchar(24),payingPhone varchar(19),contractors int default 0);
CREATE TABLE purchases (id integer primary key autoincrement,productName_id int default 0,sub int,price double default 0,qty double default 1,sum double default 0,section int default 1,taxCode int default -1,productTypeCode int default -1,paymentFormCode int default -1,nomenclatureCode varchar(192),agent int default -1,supplierName varchar(256),supplierINN varchar(12),supplierPhone varchar(19),transfName varchar(64),transfINN varchar(12),transfAddress varchar(256),transfPhone varchar(19),payingOp varchar(24),payingPhone varchar(19),operatorPhone varchar(19),countryOfOrigin varchar(3),customsDeclaration varchar(32),additionalAttribute varchar(64),exciseAmount double default 0,addInfo varchar(64),suppliers int default 0);
CREATE TABLE settings (id integer primary key autoincrement,table_id int,field int,visibleInTbl int default 1,width int default 0,UNIQUE(table_id,field));
CREATE TABLE contracts (id integer primary key autoincrement,name varchar(100),date_ INTEGER,taxCode int default -1,contractors int default 0,suppliers int default 0);
CREATE TABLE contractors (id integer primary key autoincrement,name varchar(100),ur_litso int default 0,inn varchar(12),address varchar(100),phone varchar(100),email varchar(50));
CREATE TABLE receipts (id integer primary key autoincrement,date_ INTEGER,shiftNum int,checkNum int,fiscalDocNum int,fiscalSign varchar(10),cash double,ecash double,prepayment double,credit double,consideration double,contract_id int,checkType int default -1,kkt int,cashier int,buyer int,bnk int,bnkSlip int,kktCheckDate INTEGER);
INSERT INTO receipts VALUES(1,1681117883,8,1,33,'3488153833',11.000000000000000888,NULL,NULL,NULL,NULL,NULL,0,0,0,0,NULL,NULL,1681117620);
INSERT INTO receipts VALUES(2,1681230722,NULL,NULL,NULL,NULL,NULL,32.999999999999998223,NULL,NULL,NULL,NULL,0,0,0,0,0,1,NULL);
INSERT INTO receipts VALUES(3,1681384095,NULL,NULL,NULL,NULL,304.00000000000000355,NULL,NULL,NULL,NULL,NULL,0,0,0,0,NULL,NULL,NULL);
CREATE TABLE receipt_items (id integer primary key autoincrement,receipts_id int,productName_id int default 0,price double default 0,qty double default 1,sum double default 0,section int default 1,taxCode int default -1,productTypeCode int default -1,paymentFormCode int default -1,nomenclatureCode_id int default 0,suppliers int default 0,spec_id int default 0);
INSERT INTO receipt_items VALUES(1,1,1,11.000000000000000888,1.0,11.000000000000000888,1,5,0,3,0,0,0);
INSERT INTO receipt_items VALUES(2,2,1,11.000000000000000888,1.0,11.000000000000000888,1,5,0,3,0,0,0);
INSERT INTO receipt_items VALUES(3,2,2,11.000000000000000888,1.0,11.000000000000000888,1,5,0,3,0,0,0);
INSERT INTO receipt_items VALUES(4,2,1,11.000000000000000888,1.0,11.000000000000000888,1,5,0,3,0,0,0);
INSERT INTO receipt_items VALUES(5,3,93,152.00000000000000177,1.0,152.00000000000000177,1,5,0,3,0,0,0);
INSERT INTO receipt_items VALUES(6,3,93,152.00000000000000177,1.0,152.00000000000000177,1,5,0,3,0,0,0);
CREATE TABLE bank_slips (id integer primary key autoincrement,date_ INTEGER,slip varchar(1024),type int default -1);
INSERT INTO bank_slips VALUES(1,1681230722,replace(replace('ОШИБКА\r\nКод ошибки:212 (212)\r\nОтказ ввода карты\r\n','\r',char(13)),'\n',char(10)),-1);
CREATE TABLE receipt_item_spec (id integer primary key autoincrement,receipt_item_id int,countryOfOrigin varchar(3),customsDeclaration varchar(32),additionalAttribute varchar(64),exciseAmount double default 0,addInfo varchar(512));
CREATE TABLE receipt_item_markirovka (id integer primary key autoincrement,receipt_item_id int,nomenclatureCode varchar(129));
CREATE TABLE equipment (id integer primary key autoincrement,sId varchar(150),typ int default 0,json varchar(0),hidden int default 0,turnedOn int default 0,gr int default 0);
INSERT INTO equipment VALUES(1,'АТОЛ FPrint-22ПТК 00106302835631',2,replace(replace('{\r\n    "curTaxSystem": 0,\r\n    "deviceID": "atol1",\r\n    "srvIp": "localhost",\r\n    "srvIpPort": "16732"\r\n}','\r',char(13)),'\n',char(10)),1,0,1);
CREATE TABLE cashiers (id integer primary key autoincrement,fio varchar(100),inn varchar(12),pw varchar(12),UNIQUE(fio));
CREATE TABLE config (id integer primary key autoincrement,table_id int,typ int,json varchar(0),UNIQUE(table_id,typ));
INSERT INTO config VALUES(1,6,1,replace(replace('{\r\n    "sz": {\r\n        "h": 850,\r\n        "w": 1311\r\n    },\r\n    "tabNN": 0\r\n}','\r',char(13)),'\n',char(10)));
INSERT INTO config VALUES(2,2,1,replace(replace('{\r\n    "sz": {\r\n        "h": 771,\r\n        "w": 770\r\n    },\r\n    "tabNN": 0\r\n}','\r',char(13)),'\n',char(10)));
CREATE TABLE goods (id integer primary key autoincrement,productName varchar(100),price double,section int default 1,taxCode int default -1,productTypeCode int default -1,paymentFormCode int default -1,markirovka_yes int default 0,suppliers int default 0,sub_on int default 0,remainder double default 0,unit varchar(0));
INSERT INTO goods VALUES(62,'товар 3333333333',2443.9999999999999502,1,-1,0,-1,0,0,1,0.0,'1');
INSERT INTO goods VALUES(63,'товар 333',1110.9999999999999875,1,-1,0,-1,0,0,1,0.0,'1');
INSERT INTO goods VALUES(64,'2222',333.0000000000000071,1,-1,0,-1,0,0,0,0.0,'0');
INSERT INTO goods VALUES(65,'5555',110.00000000000000888,1,-1,0,-1,0,0,0,0.0,'0');
INSERT INTO goods VALUES(66,'77777',0.0,1,-1,0,-1,0,0,1,0.0,'1');
INSERT INTO goods VALUES(67,'njdfh rere',11.000000000000000888,1,-1,0,-1,0,0,1,0.0,'1');
INSERT INTO goods VALUES(68,'njjjj',5554.9999999999997157,1,-1,0,-1,0,0,0,0.0,'');
INSERT INTO goods VALUES(69,'2222',1110.9999999999999875,1,-1,0,-1,0,0,1,0.0,'1');
INSERT INTO goods VALUES(70,'2222',11.000000000000000888,1,-1,0,-1,0,0,1,0.0,'');
INSERT INTO goods VALUES(71,'VVVVVVVVVVVVVV4544VVVVVVVVV',NULL,1,-1,-1,-1,0,0,0,0.0,NULL);
INSERT INTO goods VALUES(72,'VVVVVVVVVVVVVV4544VVVVVVVVV',NULL,1,-1,-1,-1,0,0,0,0.0,NULL);
INSERT INTO goods VALUES(73,'VVVVVVVVVVVVVV4544VVVVVVVVV',NULL,1,-1,-1,-1,0,0,0,0.0,NULL);
INSERT INTO goods VALUES(74,'VVVVVVVVVVVVVV4544VVVVVVVVV',NULL,1,-1,-1,-1,0,0,0,0.0,NULL);
INSERT INTO goods VALUES(75,'VVVVVVVVVVVVVV4544VVVVVVVVV',NULL,1,-1,-1,-1,0,0,0,0.0,NULL);
INSERT INTO goods VALUES(76,'63563564365334635',NULL,1,-1,-1,-1,0,0,0,0.0,NULL);
INSERT INTO goods VALUES(77,'63563564365334635',NULL,1,-1,-1,-1,0,0,0,0.0,NULL);
CREATE TABLE sub (id integer primary key autoincrement,goods_id int NOT NULL,value varchar(0),remainder double default 0,FOREIGN KEY(goods_id) REFERENCES goods(goods_id) ON DELETE CASCADE);
INSERT INTO sub VALUES(54,62,'M',0.0);
INSERT INTO sub VALUES(55,62,'XL',0.0);
INSERT INTO sub VALUES(56,62,'XL',0.0);
INSERT INTO sub VALUES(57,62,'XL',0.0);
INSERT INTO sub VALUES(58,62,'XL',0.0);
INSERT INTO sub VALUES(59,62,'L',0.0);
INSERT INTO sub VALUES(60,62,'M',0.0);
INSERT INTO sub VALUES(61,62,'M',0.0);
INSERT INTO sub VALUES(62,62,'Tt',0.0);
INSERT INTO sub VALUES(63,62,'Hh',0.0);
INSERT INTO sub VALUES(64,62,'Рр',0.0);
INSERT INTO sub VALUES(65,62,'Рр',0.0);
DELETE FROM sqlite_sequence;
INSERT INTO sqlite_sequence VALUES('purchases',31);
INSERT INTO sqlite_sequence VALUES('equipment',1);
INSERT INTO sqlite_sequence VALUES('receipts',3);
INSERT INTO sqlite_sequence VALUES('receipt_items',6);
INSERT INTO sqlite_sequence VALUES('config',2);
INSERT INTO sqlite_sequence VALUES('bank_slips',1);
INSERT INTO sqlite_sequence VALUES('goods',77);
INSERT INTO sqlite_sequence VALUES('sub',65);
COMMIT;

Далее вводим команду установки пароля:

// установка пароля для чистой базы
sqlite> PRAGMA key='1111';
ok

// установка пароля, если база не пустая
PRAGMA rekey='1111';
ok

// замена пароля 
sqlite> PRAGMA key='1111';  // для не пустой базы
ok
PRAGMA rekey='2222'; // заменили пароль
ok

// сброс пароля
PRAGMA rekey='2222'; // для не пустой базы
ok
PRAGMA rekey=''; // сбросили пароль и база открывается как обычно
ok


// открытие зашифрованной базы, всегда сначала идет PRAGMA key
sqlite> PRAGMA key='1111';
ok

PRAGMA rekey='....' надо всегда вводить первой командой (конечно если пароль на базе установлен). 

Далее первый параметр при запуске программы будет имя файла базы данных (у нас aaa.db). Запускаем программу, появляется консоль и надо ввести  PRAGMA key='1111'; (наш установленный ранее пароль) и только после этого база начнет выполнять команды типа SELECT * FROM goods; 

Если не ввести пароль или ввести не правильно получим что-то типа такого: Error: file is not a database;
Но база не вылетит.

Таким образом SQLeet можно нормально использовать как отдельный проект. Но вот другой вопрос - а как это все корректно добавить в Qt 4.8.1.

Вроде бы sqlite3.c можно просто заменить в исходниках ..\QtSDK1.2.1\QtSources\4.8.1\src\3rdparty\sqlite.

И оказывается его можно заменить и все соберется. И даже будет работать! 

Для этого надо файлы:
sqlite3.c ,
sqlite3.h ,
rekeyvacuum.c ,
shell.c
crypto.c
перенести в каталог исходников Qt (у нас 4.8.1 D:\QtSDK1.2.1\QtSources\4.8.1\src\3rdparty\sqlite). Старые файлы лучше упаковать в архив и не удалять пока (на всякий случай).

Тогда исходники Qt пересоберутся нормально. Пересобирать достаточно ветки plugins\sqldrivers\sqlite и наверное sql.

shell.c это получается тестовый проект по сути и в Qt нам его в принципе не надо тащить.

Можно включить дефайн SQLITE_DEBUG в sqlite3.c.

Немного о том как работает sqleet.

На ввод PRAGMA key='....' происходит вызов функции sqlite3_key_v2 в файле sqleet.c.

Вот стек вызова до функции sqlite3_key_v2  из программы:

0	sqlite3_key_v2	sqleet.c	558	0x176613	
1	sqlite3Pragma	sqlite3.c	126575	0x146baa	
2	yy_reduce	sqlite3.c	156618	0x12bce2	
3	sqlite3Parser	sqlite3.c	157182	0x129447	
4	sqlite3RunParser	sqlite3.c	158455	0x128eb8	
5	sqlite3Prepare	sqlite3.c	127575	0x11fe3e	
6	sqlite3LockAndPrepare	sqlite3.c	127647	0x1203aa	
7	sqlite3_prepare_v2	sqlite3.c	127732	0x120474	
8	shell_exec	shell.c	11614	0x1ac0cc	
9	runOneSqlLine	shell.c	18305	0x1bcc57	
10	process_input	shell.c	18405	0x1bc6ca	
11	main	shell.c	19172	0x1abaa2	
12	__tmainCRTStartup	crtexe.c	555	0x1c509f	
13	mainCRTStartup	crtexe.c	371	0x1c4ecf	
14	BaseThreadInitThunk	KERNEL32		0x778600c9	
15	__RtlUserThreadStart	ntdll		0x779a7b4e	
16	_RtlUserThreadStart	ntdll		0x779a7b1e	

Видно, что вызывать надо sqlite3_prepare_v2  ( не sqlite3_exec). Но это еще не все. На самом деле полный цикл выполнения команды sql такой:

        rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zLeftover);

        if( SQLITE_OK != rc )
        {
            if( pzErrMsg ){
                *pzErrMsg = save_err_msg(db);
            }
        }
        else
        {
            if( !pStmt )
            {
                /* this happens for a comment or white-space */
                zSql = zLeftover;
                while( IsSpace(zSql[0]) ) zSql  ;
                continue;
            }
            zStmtSql = sqlite3_sql(pStmt);
            if( zStmtSql==0 ) zStmtSql = "";
            while( IsSpace(zStmtSql[0]) ) zStmtSql  ;

            /* save off the prepared statment handle and reset row count */
            if( pArg ){
                pArg->pStmt = pStmt;
                pArg->cnt = 0;
            }

            /* echo the sql statement if echo on */
            if( pArg && ShellHasFlag(pArg, SHFLG_Echo) ){
                utf8_printf(pArg->out, "%s\n", zStmtSql ? zStmtSql : zSql);
            }

            /* Show the EXPLAIN QUERY PLAN if .eqp is on */
            if( pArg && pArg->autoEQP && sqlite3_stmt_isexplain(pStmt)==0 )
            {
                sqlite3_stmt *pExplain;
                char *zEQP;
                int triggerEQP = 0;
                disable_debug_trace_modes();
                sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, -1, &triggerEQP);
                if( pArg->autoEQP>=AUTOEQP_trigger ){
                    sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, 1, 0);
                }
                zEQP = sqlite3_mprintf("EXPLAIN QUERY PLAN %s", zStmtSql);
                rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0);
                if( rc==SQLITE_OK ){
                    while( sqlite3_step(pExplain)==SQLITE_ROW ){
                        const char *zEQPLine = (const char*)sqlite3_column_text(pExplain,3);
                        int iEqpId = sqlite3_column_int(pExplain, 0);
                        int iParentId = sqlite3_column_int(pExplain, 1);
                        if( zEQPLine[0]=='-' ) eqp_render(pArg);
                        eqp_append(pArg, iEqpId, iParentId, zEQPLine);
                    }
                    eqp_render(pArg);
                }
                sqlite3_finalize(pExplain);
                sqlite3_free(zEQP);
                if( pArg->autoEQP>=AUTOEQP_full )
                {
                    /* Also do an EXPLAIN for ".eqp full" mode */
                    zEQP = sqlite3_mprintf("EXPLAIN %s", zStmtSql);
                    rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0);

                    if( rc==SQLITE_OK ){
                        pArg->cMode = MODE_Explain;
                        explain_data_prepare(pArg, pExplain);
                        exec_prepared_stmt(pArg, pExplain);
                        explain_data_delete(pArg);
                    }
                    sqlite3_finalize(pExplain);
                    sqlite3_free(zEQP);
                }
                if( pArg->autoEQP>=AUTOEQP_trigger && triggerEQP==0 )
                {
                    sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, 0, 0);
                    /* Reprepare pStmt before reactiving trace modes */
                    sqlite3_finalize(pStmt);
                    sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
                    if( pArg ) pArg->pStmt = pStmt;
                }
                restore_debug_trace_modes();
            }

            if( pArg ){
                pArg->cMode = pArg->mode;
                if( pArg->autoExplain ){
                    if( sqlite3_stmt_isexplain(pStmt)==1 ){
                        pArg->cMode = MODE_Explain;
                    }
                    if( sqlite3_stmt_isexplain(pStmt)==2 ){
                        pArg->cMode = MODE_EQP;
                    }
                }

                /* If the shell is currently in ".explain" mode, gather the extra
        ** data required to add indents to the output.*/
                if( pArg->cMode==MODE_Explain ){
                    explain_data_prepare(pArg, pStmt);
                }
            }

            bind_prepared_stmt(pArg, pStmt);
            exec_prepared_stmt(pArg, pStmt);
            explain_data_delete(pArg);
            eqp_render(pArg);

            /* print usage stats if stats on */
            if( pArg && pArg->statsOn ){
                display_stats(db, pArg, 0);
            }

            /* print loop-counters if required */
            if( pArg && pArg->scanstatsOn ){
                display_scanstats(db, pArg);
            }

            /* Finalize the statement just executed. If this fails, save a
      ** copy of the error message. Otherwise, set zSql to point to the
      ** next statement to execute. */
            rc2 = sqlite3_finalize(pStmt);
            if( rc!=SQLITE_NOMEM ) rc = rc2;
            if( rc==SQLITE_OK ){
                zSql = zLeftover;
                while( IsSpace(zSql[0]) ) zSql  ;
            }else if( pzErrMsg ){
                *pzErrMsg = save_err_msg(db);
            }

            /* clear saved stmt handle */
            if( pArg ){
                pArg->pStmt = NULL;
            }
        }
    } /* end while */