diff --git a/include/SQLiteCpp/Database.h b/include/SQLiteCpp/Database.h index f3d0b0b..fd624d5 100644 --- a/include/SQLiteCpp/Database.h +++ b/include/SQLiteCpp/Database.h @@ -84,15 +84,13 @@ public: * @param[in] aFlags SQLite::OPEN_READONLY/SQLite::OPEN_READWRITE/SQLite::OPEN_CREATE... * @param[in] aBusyTimeoutMs Amount of milliseconds to wait before returning SQLITE_BUSY (see setBusyTimeout()) * @param[in] apVfs UTF-8 name of custom VFS to use, or nullptr for sqlite3 default - * @param[in] apPass Key to decrypt (end encrypt) the database, or nullptr for unencrypted databases * * @throw SQLite::Exception in case of error */ Database(const char* apFilename, const int aFlags = SQLite::OPEN_READONLY, const int aBusyTimeoutMs = 0, - const char* apVfs = NULL, - const char* apPass = NULL); + const char* apVfs = NULL); /** * @brief Open the provided database UTF-8 filename. @@ -108,15 +106,13 @@ public: * @param[in] aFlags SQLite::OPEN_READONLY/SQLite::OPEN_READWRITE/SQLite::OPEN_CREATE... * @param[in] aBusyTimeoutMs Amount of milliseconds to wait before returning SQLITE_BUSY (see setBusyTimeout()) * @param[in] aVfs UTF-8 name of custom VFS to use, or empty string for sqlite3 default - * @param[in] aPass Key to decrypt (end encrypt) the database, or empty string for unencrypted databases * * @throw SQLite::Exception in case of error */ Database(const std::string& aFilename, const int aFlags = SQLite::OPEN_READONLY, const int aBusyTimeoutMs = 0, - const std::string& aVfs = "", - const std::string& aPass = ""); + const std::string& aVfs = ""); /** * @brief Close the SQLite database connection. @@ -373,6 +369,46 @@ public: */ void loadExtension(const char* apExtensionName, const char* apEntryPointName); + /** + * @brief Set the key for the current sqlite database instance. + * + * This is the equivalent of the sqlite3_key call and should thus be called + * directly after opening the database. If the database is unencrypted, + * this methods encrypts it immediately. + * + * @param[in] key Key to decode/encode the database + * + * @throw SQLite::Exception in case of error + */ + void key(const std::string& aKey) const noexcept; // nothrow + + /** + * @brief Reset the key for the current sqlite database instance. + * + * This is the equivalent of the sqlite3_rekey call and should thus be called + * after the database has been opened with a valid key. To decrypt a + * database, call this method with a NULL pointer. + * + * @param[in] nkey New key to encode the database + * + * @throw SQLite::Exception in case of error + */ + void rekey(const std::string& aNewKey) const noexcept; // nothrow + + /** + * @brief Test if a file contains an unencrypted database. + * + * This is a simple test that reads the first bytes of a database file and + * compares them to the standard header for unencrypted databases. If the + * header does not match the standard string, we assume that we have an + * encrypted file. + * + * @param[in] aFilename path/uri to a file + * + * @return true if the database has the standard header. + */ + const bool isUnencrypted(const std::string& aFilename) const noexcept; // nothrow + private: /// @{ Database must be non-copyable Database(const Database&); diff --git a/src/Database.cpp b/src/Database.cpp index 417a83c..30cf177 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -15,6 +15,7 @@ #include #include +#include #ifndef SQLITE_DETERMINISTIC #define SQLITE_DETERMINISTIC 0x800 @@ -51,8 +52,7 @@ int getLibVersionNumber() noexcept // nothrow Database::Database(const char* apFilename, const int aFlags /* = SQLite::OPEN_READONLY*/, const int aBusyTimeoutMs /* = 0 */, - const char* apVfs /* = NULL*/, - const char* apPass /* = NULL*/) : + const char* apVfs /* = NULL*/) : mpSQLite(NULL), mFilename(apFilename) { @@ -63,25 +63,6 @@ Database::Database(const char* apFilename, sqlite3_close(mpSQLite); // close is required even in case of error on opening throw exception; } -#ifdef SQLITE_HAS_CODEC - if (apPass != NULL) - { - ret = sqlite3_key(mpSQLite, apPass, strlen(apPass)); - if (SQLITE_OK != ret) - { - const SQLite::Exception exception(mpSQLite, ret); // must create before closing - sqlite3_close(mpSQLite); // close is required even in case of error on opening - throw exception; - } - } -#else - if (apPass != NULL) - { - const SQLite::Exception exception("No encryption support, recompile with SQLITE_HAS_CODEC or remove the password from the constructor."); // must create before closing - sqlite3_close(mpSQLite); // close is required even in case of error on opening - throw exception; - } -#endif if (aBusyTimeoutMs > 0) { setBusyTimeout(aBusyTimeoutMs); @@ -92,8 +73,7 @@ Database::Database(const char* apFilename, Database::Database(const std::string& aFilename, const int aFlags /* = SQLite::OPEN_READONLY*/, const int aBusyTimeoutMs /* = 0 */, - const std::string& aVfs /* = "" */, - const std::string& aPass /* = "" */) : + const std::string& aVfs /* = "" */) : mpSQLite(NULL), mFilename(aFilename) { @@ -104,25 +84,6 @@ Database::Database(const std::string& aFilename, sqlite3_close(mpSQLite); // close is required even in case of error on opening throw exception; } -#ifdef SQLITE_HAS_CODEC - if (!aPass.empty()) - { - ret = sqlite3_key(mpSQLite, aPass.data(), aPass.size()); - if (SQLITE_OK != ret) - { - const SQLite::Exception exception(mpSQLite, ret); // must create before closing - sqlite3_close(mpSQLite); // close is required even in case of error on opening - throw exception; - } - } -#else - if (!aPass.empty()) - { - const SQLite::Exception exception("No encryption support, recompile with SQLITE_HAS_CODEC or remove the password from the constructor."); // must create before closing - sqlite3_close(mpSQLite); // close is required even in case of error on opening - throw exception; - } -#endif if (aBusyTimeoutMs > 0) { setBusyTimeout(aBusyTimeoutMs); @@ -267,4 +228,56 @@ void Database::loadExtension(const char* apExtensionName, const char *apEntryPoi #endif } +//Set the key for the current sqlite database instance. +void Database::key(const std::string& aKey) const noexcept // nothrow +{ + int pass_len = aKey.length(); +#ifdef SQLITE_HAS_CODEC + if (pass_len > 0) { + int ret = sqlite3_key(mpSQLite, aKey.c_str(), pass_len); + check(ret); + } +#else + if (pass_len > 0) { + const SQLite::Exception exception("No encryption support, recompile with SQLITE_HAS_CODEC to use this function."); + throw exception; + } +#endif // SQLITE_HAS_CODEC +} + +// Reset the key for the current sqlite database instance. +void Database::rekey(const std::string& aNewKey) const noexcept // nothrow +{ +#ifdef SQLITE_HAS_CODEC + int pass_len = aNewKey.length(); + if (pass_len > 0) { + int ret = sqlite3_rekey(mpSQLite, aNewKey.c_str(), pass_len); + check(ret); + } else { + int ret = sqlite3_rekey(mpSQLite, nullptr, 0); + check(ret); + } +#else + const SQLite::Exception exception("No encryption support, recompile with SQLITE_HAS_CODEC to use this function."); + throw exception; +#endif // SQLITE_HAS_CODEC +} + +//Test if a file contains an unencrypted database. +const bool Database::isUnencrypted(const std::string& aFilename) const noexcept // nothrow +{ + bool encrypted_db = false; + if (aFilename.length() > 0) { + std::ifstream fileBuffer(aFilename, std::ios::in | std::ios::binary); + char header[16]; + if (fileBuffer.is_open()) { + fileBuffer.seekg(0, std::ios::beg); + fileBuffer.getline(header, 16); + fileBuffer.close(); + } + encrypted_db = strncmp(header, "SQLite format 3\000", 16) != 0; + } + return encrypted_db; +} + } // namespace SQLite