Add initial SQLCipher support · skyformat99/sqlitebrowser@9ba36d0 · GitHub
Skip to content

Commit 9ba36d0

Browse files
committed
Add initial SQLCipher support
Add some basic initial support for SQLCipher. Note that this is more of a POC than a final implementation. This commit adds an option called 'sqlcipher' to the cmake and qmake projects which - when enabled - replaces the default SQLite3 include and library files by their SQLCipher counter-parts. Especially on MacOS X there might be some more work required in finding the correct include paths. The SQLCipher library supports unencrypted databases, too, so even if the option is enabled the program behaves like before. You can see the difference, though, in the About Dialog where the SQLite version string will say 'SQLCipher version xy'. When the sqlcipher option is enabled and you try to open a file which is neither a project file nor a normal SQLite3 database it is assumed now that the file is an encypted database. There is no way to tell between an invalid file and an encypted file, so in both cases a password dialog pops up. When the correct password and page size are entered the file is opened and can be edited like any other database before. Creating encrypted databases isn't supported yet. So for testing you need to fall back to the sqlcipher command line tool. See issue sqlitebrowser#12.
1 parent e93392c commit 9ba36d0

9 files changed

Lines changed: 107 additions & 39 deletions

‎CMakeLists.txt‎

Lines changed: 12 additions & 3 deletions

‎src/AboutDialog.cpp‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#include "AboutDialog.h"
22
#include "ui_AboutDialog.h"
33
#include "gen_version.h"
4-
#include <sqlite3.h>
4+
#include "sqlite.h"
55

66
AboutDialog::AboutDialog(QWidget *parent) :
77
QDialog(parent),
@@ -12,7 +12,11 @@ AboutDialog::AboutDialog(QWidget *parent) :
1212

1313
ui->label_version->setText(ui->label_version->text() + " " + APP_VERSION);
1414
ui->label_versionqt->setText(ui->label_versionqt->text() + " " + QT_VERSION_STR);
15+
#ifdef SQLCIPHER
16+
ui->label_versionsqlite->setText(ui->label_versionsqlite->text().replace("SQLite", "SQLCipher") + " " + SQLITE_VERSION);
17+
#else
1518
ui->label_versionsqlite->setText(ui->label_versionsqlite->text() + " " + SQLITE_VERSION);
19+
#endif
1620
}
1721

1822
AboutDialog::~AboutDialog()

‎src/ExportCsvDialog.cpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
#include "sqlitedb.h"
44
#include "PreferencesDialog.h"
55
#include "sqlitetablemodel.h"
6+
#include "sqlite.h"
67

78
#include <QFile>
89
#include <QTextStream>
910
#include <QMessageBox>
1011
#include <QFileDialog>
11-
#include <sqlite3.h>
1212

1313
ExportCsvDialog::ExportCsvDialog(DBBrowserDB* db, QWidget* parent, const QString& query, const QString& selection)
1414
: QDialog(parent),

‎src/ImportCsvDialog.cpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include "ui_ImportCsvDialog.h"
33
#include "sqlitedb.h"
44
#include "csvparser.h"
5+
#include "sqlite.h"
56

67
#include <QMessageBox>
78
#include <QProgressDialog>
@@ -10,7 +11,6 @@
1011
#include <QTextCodec>
1112
#include <QCompleter>
1213
#include <QComboBox>
13-
#include <sqlite3.h>
1414
#include <QFile>
1515
#include <QTextStream>
1616
#include <QSettings>

‎src/MainWindow.cpp‎

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "VacuumDialog.h"
1616
#include "DbStructureModel.h"
1717
#include "gen_version.h"
18+
#include "sqlite.h"
1819

1920
#include <QFileDialog>
2021
#include <QFile>
@@ -28,7 +29,6 @@
2829
#include <QScrollBar>
2930
#include <QSortFilterProxyModel>
3031
#include <QElapsedTimer>
31-
#include <sqlite3.h>
3232
#include <QNetworkRequest>
3333
#include <QNetworkReply>
3434
#include <QNetworkAccessManager>
@@ -195,23 +195,30 @@ bool MainWindow::fileOpen(const QString& fileName, bool dontAddToRecentFiles)
195195
if(QFile::exists(wFile) )
196196
{
197197
fileClose();
198-
if(db.open(wFile))
198+
// Try opening it as a project file first
199+
if(loadProject(wFile))
199200
{
200-
statusEncodingLabel->setText(db.getPragma("encoding"));
201-
setCurrentFile(wFile);
202-
if(!dontAddToRecentFiles)
203-
addToRecentFilesMenu(wFile);
204201
retval = true;
205202
} else {
206-
// Failed opening file; so it might be a project file instead
207-
return loadProject(wFile);
203+
// No project file; so it should be a database file
204+
if(db.open(wFile))
205+
{
206+
statusEncodingLabel->setText(db.getPragma("encoding"));
207+
setCurrentFile(wFile);
208+
if(!dontAddToRecentFiles)
209+
addToRecentFilesMenu(wFile);
210+
openSqlTab(true);
211+
retval = true;
212+
} else {
213+
QMessageBox::warning(this, qApp->applicationName(), tr("Invalid file format."));
214+
return false;
215+
}
208216
}
209217
loadExtensionsFromSettings();
210218
populateStructure();
211219
resetBrowser();
212220
if(ui->mainTab->currentIndex() == 2)
213221
loadPragmas();
214-
openSqlTab(true);
215222
}
216223

217224
return retval;
@@ -1749,10 +1756,7 @@ bool MainWindow::loadProject(QString filename)
17491756
xml.readNext(); // token == QXmlStreamReader::StartDocument
17501757
xml.readNext(); // name == sqlb_project
17511758
if(xml.name() != "sqlb_project")
1752-
{
1753-
QMessageBox::warning(this, qApp->applicationName(), tr("Invalid file format."));
17541759
return false;
1755-
}
17561760

17571761
addToRecentFilesMenu(filename);
17581762

‎src/sqlite.h‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#ifndef SQLITE_H
2+
#define SQLITE_H
3+
4+
#ifdef SQLCIPHER
5+
#define SQLITE_TEMP_STORE 2
6+
#define SQLITE_HAS_CODEC
7+
#include <sqlcipher/sqlite3.h>
8+
#else
9+
#include <sqlite3.h>
10+
#endif
11+
12+
#endif

‎src/sqlitedb.cpp‎

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
#include "sqlitedb.h"
22
#include "sqlitetablemodel.h"
3+
#include "sqlite.h"
34

45
#include <QFile>
56
#include <QMessageBox>
67
#include <QProgressDialog>
78
#include <QApplication>
89
#include <QSettings>
910
#include <QDebug>
10-
#include <sqlite3.h>
11+
#include <QInputDialog>
1112

1213
// collation callbacks
1314
int collCompare(void* /*pArg*/, int /*eTextRepA*/, const void* sA, int /*eTextRepB*/, const void* sB)
@@ -43,32 +44,62 @@ bool DBBrowserDB::getDirty() const
4344
return !savepointList.empty();
4445
}
4546

46-
bool DBBrowserDB::open ( const QString & db)
47+
bool DBBrowserDB::open(const QString& db)
4748
{
4849
bool ok=false;
4950
int err;
5051

5152
if (isOpen()) close();
5253

53-
//try to verify the SQLite version 3 file header
54-
QFile dbfile(db);
55-
if ( dbfile.open( QIODevice::ReadOnly ) ) {
56-
char buffer[16+1];
57-
dbfile.readLine(buffer, 16);
58-
QString contents = QString(buffer);
59-
dbfile.close();
60-
if (!contents.startsWith("SQLite format 3")) {
61-
lastErrorMessage = QObject::tr("File is not a SQLite 3 database");
62-
return false;
63-
}
64-
} else {
65-
lastErrorMessage = QObject::tr("File could not be read");
54+
lastErrorMessage = QObject::tr("no error");
55+
56+
// Open database file
57+
if(sqlite3_open_v2(db.toUtf8(), &_db, SQLITE_OPEN_READWRITE, NULL) != SQLITE_OK)
58+
{
59+
lastErrorMessage = QString::fromUtf8((const char*)sqlite3_errmsg(_db));
6660
return false;
6761
}
6862

69-
lastErrorMessage = QObject::tr("no error");
70-
71-
err = sqlite3_open_v2(db.toUtf8(), &_db, SQLITE_OPEN_READWRITE, NULL);
63+
// Try reading from database
64+
bool done = false;
65+
do
66+
{
67+
QString statement = "SELECT COUNT(*) FROM sqlite_master;";
68+
QByteArray utf8Statement = statement.toUtf8();
69+
sqlite3_stmt* vm;
70+
const char* tail;
71+
err = sqlite3_prepare_v2(_db, utf8Statement, utf8Statement.length(), &vm, &tail);
72+
if(sqlite3_step(vm) != SQLITE_ROW)
73+
{
74+
#ifdef SQLCIPHER
75+
QString pass = QInputDialog::getText(0, qApp->applicationName(),
76+
QObject::tr("Couldn't read from database file. This means it is either not a valid SQLite3 "
77+
"database or it is encrypted.\nIn the latter case you can provide a passphrase to open the file. Note"
78+
"that only databases encrypted using SQLCipher are supported."));
79+
if(pass.isEmpty())
80+
{
81+
sqlite3_close(_db);
82+
_db = 0;
83+
return false;
84+
} else {
85+
int pagesize = QInputDialog::getInt(0, qApp->applicationName(),
86+
QObject::tr("If the database has been encrypted using a different page size than normally (i.e. 1024 bytes) this "
87+
"value is required, too."),
88+
1024, 0);
89+
if(pagesize == 0) pagesize = 1024;
90+
sqlite3_key(_db, pass.toUtf8(), pass.toUtf8().length());
91+
sqlite3_exec(_db, QString("PRAGMA cipher_page_size = %1;").arg(pagesize).toUtf8(), NULL, NULL, NULL);
92+
}
93+
#else
94+
sqlite3_close(_db);
95+
_db = 0;
96+
return false;
97+
#endif
98+
} else {
99+
done = true;
100+
}
101+
sqlite3_finalize(vm);
102+
} while(!done);
72103

73104
// register collation callback
74105
sqlite3_collation_needed(_db, NULL, collation_needed);

‎src/sqlitetablemodel.cpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
#include "sqlitetablemodel.h"
22
#include "sqlitedb.h"
3+
#include "sqlite.h"
34

45
#include <QDebug>
56
#include <QMessageBox>
67
#include <QApplication>
7-
#include <sqlite3.h>
88

99
SqliteTableModel::SqliteTableModel(QObject* parent, DBBrowserDB* db, size_t chunkSize)
1010
: QAbstractTableModel(parent)

‎src/src.pro‎

Lines changed: 10 additions & 2 deletions

0 commit comments

Comments
 (0)