Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ jsconfig.json
# Xcode
#
build/
!cpp/build/
cpp/build/*
!cpp/build/generated/
*.pbxuser
!default.pbxuser
*.mode1v3
Expand Down Expand Up @@ -76,7 +79,3 @@ android/keystores/debug.keystore

# generated by bob
lib/

# React Native Codegen
ios/generated
android/generated
97 changes: 97 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,103 @@ const selectResult = db.executeSql("SELECT * FROM users", []);
console.log("Select result:", selectResult);
```

## Encryption Support (SQLCipher)

This library supports database encryption via [SQLCipher](https://www.zetetic.net/sqlcipher/). Encryption is **opt-in** and configured at build time.

### Enabling SQLCipher

Add the following configuration to your app's `package.json`:

```json
{
"react-native-turbo-sqlite": {
"sqlcipher": true
}
}
```

**For monorepos:** Add the configuration to the root `package.json` of your monorepo.

After adding the configuration:

1. **iOS/macOS**: Run `pod install` in the `ios` directory
2. **Android**: Add OpenSSL dependency and rebuild (see Android setup below)

### Using Encrypted Databases

Once SQLCipher is enabled, pass an encryption key when opening a database:

```js
import TurboSqlite from "react-native-turbo-sqlite";
import { DocumentDirectoryPath } from "@dr.pogodin/react-native-fs";

// Open an encrypted database
const db = TurboSqlite.openDatabase(
DocumentDirectoryPath + "/encrypted.db",
"my-secret-encryption-key"
);

// Use the database normally
db.executeSql("CREATE TABLE IF NOT EXISTS secrets (id INTEGER PRIMARY KEY, data TEXT)", []);
```

### Important Notes

- **Without the encryption key**, SQLCipher databases cannot be opened
- **Changing the key** requires re-encrypting the entire database
- **Standard SQLite** databases can still be opened without providing a key
- **Key storage**: Store your encryption keys securely (e.g., using [react-native-keychain](https://github.com/oblador/react-native-keychain))

### Security Considerations

- Never hardcode encryption keys in your source code
- Use platform-specific secure storage for keys (Keychain on iOS, Keystore on Android)
- Consider using user-derived keys (e.g., from a password or biometric authentication)
- The encryption key is passed as a string and can be any length (recommended: 32+ characters)

### Platform-Specific Setup

#### iOS/macOS

No additional setup required. Uses CommonCrypto (via Security.framework) which is built-in.

**Note:** For macOS, remember to set `ENV['RCT_NEW_ARCH_ENABLED'] = '1'` in your `Podfile`.

#### Android

SQLCipher on Android requires OpenSSL. Add the following to your app's `android/app/build.gradle`:

```gradle
android {
buildFeatures {
prefab true
}
}

dependencies {
// Add OpenSSL dependency for SQLCipher
implementation 'io.github.ronickg:openssl:3.3.3'
}
```

**Notes:**
- The OpenSSL version 3.3.3 is recommended (matches SQLCipher's requirements)
- Prefab is Android's modern system for native dependencies
- This will add ~6-7 MB to your APK (all architectures) or ~2-3 MB per architecture with APK splits

#### App Size Impact

- **Android**: ~6-7 MB (all architectures) or ~2-3 MB per architecture with APK splits
- **iOS/macOS**: ~1-2 MB per architecture

### Disabling Encryption

To switch back to standard SQLite:

1. Remove the configuration from `package.json`
2. Run `pod install` (iOS/macOS) or rebuild (Android)

## Why yet another sqlite lib?

Current sqlite libs for react-native such as op-sqlite and react-native-quick-sqlite do not support
Expand Down
64 changes: 58 additions & 6 deletions cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,42 @@ project(TurboSqlite)
set(CMAKE_VERBOSE_MAKEFILE on)
set(CMAKE_CXX_STANDARD 20)

# Separate C and C++ files
file(GLOB c_source_files CONFIGURE_DEPENDS *.c)
file(GLOB cpp_source_files CONFIGURE_DEPENDS *.cpp)
# Check for SQLCipher configuration
execute_process(
COMMAND node "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/check-sqlcipher-config.js"
OUTPUT_VARIABLE USE_SQLCIPHER_OUTPUT
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE CHECK_CONFIG_RESULT
)

if(CHECK_CONFIG_RESULT EQUAL 0 AND USE_SQLCIPHER_OUTPUT STREQUAL "1")
set(USE_SQLCIPHER ON)
message(STATUS "[react-native-turbo-sqlite] Building with SQLCipher support enabled")
else()
set(USE_SQLCIPHER OFF)
message(STATUS "[react-native-turbo-sqlite] Building with standard SQLite (no encryption)")
endif()

# Add SQLite compilation flags
# add_compile_definitions(SQLITE_ENABLE_RTREE SQLITE_ENABLE_FTS4 SQLITE_ENABLE_FTS5 SQLITE_ENABLE_JSON1 SQLITE_ENABLE_RBU SQLITE_ENABLE_SESSION)
# Separate C and C++ files
if(USE_SQLCIPHER)
# Use SQLCipher amalgamation
file(GLOB c_source_files CONFIGURE_DEPENDS sqlcipher/*.c)
file(GLOB cpp_source_files CONFIGURE_DEPENDS *.cpp)
else()
# Use standard SQLite
file(GLOB c_source_files CONFIGURE_DEPENDS sqlite3.c)
file(GLOB cpp_source_files CONFIGURE_DEPENDS *.cpp)
endif()

# Build a static library from the source files
add_library(TurboSqlite STATIC ${c_source_files} ${cpp_source_files})

# Add headers search paths
target_include_directories(TurboSqlite PUBLIC .)
if(USE_SQLCIPHER)
target_include_directories(TurboSqlite PUBLIC . sqlcipher)
else()
target_include_directories(TurboSqlite PUBLIC .)
endif()

# Set C++ specific compile options
set(CXX_COMPILE_OPTIONS -fexceptions -frtti -std=c++20)
Expand All @@ -26,5 +50,33 @@ target_compile_options(TurboSqlite PRIVATE
$<$<COMPILE_LANGUAGE:CXX>:${CXX_COMPILE_OPTIONS}>
)

# SQLCipher specific configuration
if(USE_SQLCIPHER)
# Add SQLCipher compile definitions
target_compile_definitions(TurboSqlite PRIVATE
SQLITE_HAS_CODEC
SQLITE_TEMP_STORE=2
SQLITE_EXTRA_INIT=sqlcipher_extra_init
SQLITE_EXTRA_SHUTDOWN=sqlcipher_extra_shutdown
)

# Find and link OpenSSL (required for Android, not needed for iOS/macOS which use CommonCrypto)
if(ANDROID)
# Add OpenSSL crypto provider flag for Android
target_compile_definitions(TurboSqlite PRIVATE
SQLCIPHER_CRYPTO_OPENSSL
)
# Use Prefab to find OpenSSL package
find_package(openssl REQUIRED CONFIG)
target_link_libraries(TurboSqlite openssl::crypto)
# Link Android log library (required for SQLCipher logging)
target_link_libraries(TurboSqlite log)
message(STATUS "[react-native-turbo-sqlite] Linked against OpenSSL via Prefab and Android log")
else()
# iOS/macOS use CommonCrypto via Security.framework (configured in podspec)
message(STATUS "[react-native-turbo-sqlite] Using CommonCrypto (iOS/macOS)")
endif()
endif()

# Finally link the library with the codegen specs
target_link_libraries(TurboSqlite react_codegen_RNTurboSqliteSpec)
4 changes: 4 additions & 0 deletions cpp/DatabaseHostObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@

#include <jsi/jsi.h>

#ifdef SQLITE_HAS_CODEC
#include "sqlcipher/sqlite3.h"
#else
#include "sqlite3.h"
#endif

namespace facebook::react {

Expand Down
17 changes: 16 additions & 1 deletion cpp/TurboSqliteModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ std::string TurboSqliteModule::getVersionString(facebook::jsi::Runtime& runtime)
return version;
}

jsi::Object TurboSqliteModule::openDatabase(jsi::Runtime& runtime, std::string name) {
jsi::Object TurboSqliteModule::openDatabase(jsi::Runtime& runtime, std::string name, std::optional<std::string> encryptionKey) {
// Create folders if they don't exist
std::filesystem::path p(name);
if (!p.has_parent_path() || p.parent_path().empty()) {
Expand All @@ -36,6 +36,21 @@ jsi::Object TurboSqliteModule::openDatabase(jsi::Runtime& runtime, std::string n
throw jsi::JSError(runtime, error_message);
}

// Set encryption key if provided
if (encryptionKey.has_value() && !encryptionKey.value().empty()) {
#ifdef SQLITE_HAS_CODEC
rc = sqlite3_key(db, encryptionKey.value().c_str(), static_cast<int>(encryptionKey.value().length()));
if (rc != SQLITE_OK) {
std::string error_message = "Failed to set encryption key: " + std::string(sqlite3_errmsg(db));
sqlite3_close(db);
throw jsi::JSError(runtime, error_message);
}
#else
sqlite3_close(db);
throw jsi::JSError(runtime, "Encryption key provided but library was not built with SQLCipher support. Enable SQLCipher in package.json and rebuild.");
#endif
}

auto hostObject = std::make_shared<DatabaseHostObject>(db);
return jsi::Object::createFromHostObject(runtime, hostObject);
}
Expand Down
6 changes: 5 additions & 1 deletion cpp/TurboSqliteModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@

#include <string>

#ifdef SQLITE_HAS_CODEC
#include "sqlcipher/sqlite3.h"
#else
#include "sqlite3.h"
#endif

namespace facebook::react {

Expand All @@ -20,7 +24,7 @@ class TurboSqliteModule : public NativeTurboSqliteCxxSpec<TurboSqliteModule> {

std::string getVersionString(facebook::jsi::Runtime& runtime);

jsi::Object openDatabase(jsi::Runtime& runtime, std::string name);
jsi::Object openDatabase(jsi::Runtime& runtime, std::string name, std::optional<std::string> encryptionKey);
};

} // namespace facebook::react
36 changes: 36 additions & 0 deletions cpp/build/generated/source/codegen/jni/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)

file(GLOB react_codegen_SRCS CONFIGURE_DEPENDS *.cpp react/renderer/components/RNTurboSqliteSpec/*.cpp)

add_library(
react_codegen_RNTurboSqliteSpec
OBJECT
${react_codegen_SRCS}
)

target_include_directories(react_codegen_RNTurboSqliteSpec PUBLIC . react/renderer/components/RNTurboSqliteSpec)

target_link_libraries(
react_codegen_RNTurboSqliteSpec
fbjni
jsi
# We need to link different libraries based on whether we are building rncore or not, that's necessary
# because we want to break a circular dependency between react_codegen_rncore and reactnative
reactnative
)

target_compile_options(
react_codegen_RNTurboSqliteSpec
PRIVATE
-DLOG_TAG=\"ReactNative\"
-fexceptions
-frtti
-std=c++20
-Wall
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GenerateModuleJniCpp.js
*/

#include "RNTurboSqliteSpec.h"

namespace facebook::react {



std::shared_ptr<TurboModule> RNTurboSqliteSpec_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams &params) {

return nullptr;
}

} // namespace facebook::react
24 changes: 24 additions & 0 deletions cpp/build/generated/source/codegen/jni/RNTurboSqliteSpec.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@

/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GenerateModuleJniH.js
*/

#pragma once

#include <ReactCommon/JavaTurboModule.h>
#include <ReactCommon/TurboModule.h>
#include <jsi/jsi.h>

namespace facebook::react {



JSI_EXPORT
std::shared_ptr<TurboModule> RNTurboSqliteSpec_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams &params);

} // namespace facebook::react
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GenerateComponentDescriptorCpp.js
*/

#include <react/renderer/components/RNTurboSqliteSpec/ComponentDescriptors.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>

namespace facebook::react {

void RNTurboSqliteSpec_registerComponentDescriptorsFromCodegen(
std::shared_ptr<const ComponentDescriptorProviderRegistry> registry) {

}

} // namespace facebook::react
Loading