sh1’s diary

プログラミング、読んだ本、資格試験、ゲームとか私を記録するところ

Unity SQLiteUnityKit 暗号化 SQLCipher (SQLite) を Windows で利用する(DLL コンパイル)

この記事は、SQLite に暗号化機能を加えた「SQLCipher」を(フリーで)Unity (Windows) 向け に DLL をコンパイルする方法を記録したものです。

f:id:shikaku_sh:20200214142400p:plain:w400
SQLite3 が暗号化に対応した SQLCipher

ここでの SQLCipher は、オープンソース版の「SQLCipher Community Edition」です。

SQLiteUnityKit 用の SQLCipher を用意するには

SQLiteUnityKit は、ネイティブな C ライブラリーを DllImport で呼び出してデータベースを操作しています。

なので、SQLCipher もなるべくシンプルな DLL を作成します。(自分でコンパイルしてやる必要があるという意味です)さっぱりと「DllImport を通じて定義されている関数を呼び出せるだけ」というライブラリーがゴールになっています。

Android のアプリにも対応させるなら .so ファイルも同様のものを作成してやる必要があります。「SQLCipher for Android Application Integration」で使い方を説明してくれていますが、JAVA を利用しているため、「SQLiteUnityKit」を基本にしたい身としては、不都合です……というか、今からつくる DLL と整合性がとれません。(なので、ここの記事がやりたいことになります。

Android 用の so ファイルのビルドは、こっちの記事を参照。

環境設定

不要かもしれない(けど、入れたもの)

OpenSSL のインストール

OpenSSL は公式からダウンロードして、自分でコンパイルしてもよいけど、以下のあたりの lib(ライブラリー)を参照するため用意する必要があります。ここでは環境設定のリンクからインストーラーを利用してインストールしました。(これだと全部用意してくれてる)

f:id:shikaku_sh:20200214142939p:plain:w500
ここからダウンロード

  • libapps.lib
  • libcrypto_static.lib
  • libssl_static.lib
  • ossltest.lib
  • capi.lib
  • libcrypto.def
  • libssl.def
  • libtestutil.lib
  • padlock.lib
  • dasync.lib
  • libcrypto.lib
  • libssl.lib
  • openssl.lib
  • uitest.lib

インストール設定は、すべてデフォルトで問題ないです。

Light を利用していないので、Light でいいかは知らないです。

ここでは以下の URL にインストールしました。あとの記事でも、この OpenSSL のパスを直接入力しているところがあるので、その部分は置き換えてください。

  • C:\openssl_x64
  • C:\openssl_x86

SQLCipher のリポジトリーをクローン

Visual Studio, Git Bash などなにを使ってもいいですが、ここでは Visual Studio を利用します。

Visual Studio のツール「x64/x86 Native Tools Command Prompt for VS 2019」を使って、あとでコンパイルするため、ここでは必須です。

「コードをクローンまたはチェックアウトする」から、リポジトリーをクローンします。

f:id:shikaku_sh:20200214143150p:plain:w200
スプライトウィンドウ

こんな感じで、OpenSSL と SQLCipher を利用する環境が整いました。

f:id:shikaku_sh:20200214143238p:plain:w500
Visual Studio にクローンしてもらう

f:id:shikaku_sh:20200214143359p:plain:w500
こんな感じで開きました

クローンするリポジトリーは x86/x64 用で2つ作っておくのも、環境を保存する意味でよいと思います。

Makefile.msc の編集

Makefile は、コンパイルの条件設定を作成するファイルです。なので、詳しい詳細は作った人にしか正直わからないため、細かいミスや環境の違い等でうまく通らないと面倒になる部分です。(個人的には一番注意したいところ)

(x64/x86) の違いについて

つぎの「設定値の修正」の項目では、x64/x86 のコードを併記している部分があります。

どちらかのコードだけを書くようにして、コンパイルは別々にする必要があります。(クローンするリポジトリーを2つにしておくのもよいと思います)

設定値の修正

272, 275 行目(細かい行番号は以下を含めて、クローンされたバージョンによってズレてるかも)のコードを以下のように変更しました。

f:id:shikaku_sh:20200214143513p:plain:w500

# 変更前
SQLITE3DLL = ******.dll
# 変更後
SQLITE3DLL = sqlcipher.dll

283, 285 行目のコードを以下のように変更しました。

f:id:shikaku_sh:20200214143634p:plain:w500

# 変更前
SQLITE3LIB = ******.lib
# 変更後
SQLITE3LIB = sqlcipher.lib

293, 295 行目のコードを以下のように変更しました。

f:id:shikaku_sh:20200214143728p:plain:w500

# 変更前
SQLITE3EXE = ******.exe
# 変更後
SQLITE3EXE = sqlcipher.exe

306 行目のコードを以下のように変更しました。

f:id:shikaku_sh:20200214143821p:plain:w500

# 変更前
SQLITE3EXEPDB = /pdb:sqlite3sh.pdb
# 変更後
SQLITE3EXEPDB = /pdb:sqlciphersh.pdb

988, 989 行目のコードを以下のように「1」から「2」に変更しました。書かれている場所は、 (-DSQLITE_TEMP_STORE) で検索すると飛びやすい。

f:id:shikaku_sh:20200214143906p:plain:w500

# 変更前
TCC = $(TCC) -DSQLITE_TEMP_STORE=1
RCC = $(RCC) -DSQLITE_TEMP_STORE=1
# 変更後
TCC = $(TCC) -DSQLITE_TEMP_STORE=2
RCC = $(RCC) -DSQLITE_TEMP_STORE=2

989 行目の下に以下のコードを書き足します。(書き換えではなくて、新しく書き加えます)

f:id:shikaku_sh:20200214143952p:plain:w500

# Add -DSQLITE_HAS_CODEC to TCC and RCC
TCC = $(TCC) -DSQLITE_HAS_CODEC
RCC = $(RCC) -DSQLITE_HAS_CODEC

さらに、以下のコードも新しく書き加えます。(x86/x64 でどちらかを入れてください)

f:id:shikaku_sh:20200214144030p:plain:w500

# Add OpenSSL include Path (x86)
TCC = $(TCC) -IC:C:\openssl_x86\include
RCC = $(RCC) -IC:C:\openssl_x86\include

# Add OpenSSL include Path (x64)
TCC = $(TCC) -IC:C:\openssl_x64\include
RCC = $(RCC) -IC:C:\openssl_x64\include

最後に、以下のコードを新しく書き加えます。/LIBPATH:C:~ のコードは例のように同じ行で(1行)で書きます。

f:id:shikaku_sh:20200214144121p:plain:w500

# Add OpenSSL Library path and Library filenames (x86)
LTLIBPATHS = $(LTLIBPATHS) /LIBPATH:C:\openssl_x86\lib /LIBPATH:C:\openssl_x86\lib\VC
LTLIBS = $(LTLIBS) capi.lib padlock.lib libcrypto.lib libssl.lib

# Add OpenSSL Library path and Library filenames (x64)
LTLIBPATHS = $(LTLIBPATHS) /LIBPATH:C:\openssl_x64\lib /LIBPATH:C:\openssl_x64\lib\VC
LTLIBS = $(LTLIBS) capi.lib padlock.lib libcrypto.lib libssl.lib

If ICU support is enabled で検索して、その上に加えました

コンパイル

コンパイル用の「Native Tools Command Prompt for VS 2019」を起動します。x86/x64 のどちらかをで起動します。(念のため、管理者として実行しています)

f:id:shikaku_sh:20200214144320p:plain:w500
x64/x86 の使い分けに注意します

cd で sqlcipher をクローンしたフォルダーに移動して、n make /f Makefile.msc で編集した makefile を実行します。

f:id:shikaku_sh:20200214144409p:plain:w500
コンパイルを実行します!

cd "#リポジトリーのパス#"
nmake /f Makefile.msc

エラーがでるとき

環境によって、以下のように openssl の include が開けないというエラーが出ることがあります。

sqlite3.c(24328): fatal error C1083: include ファイルを開けません。'openssl/rand.h':No such file or directory
NMAKE : fatal error U1077: '"C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.24.28314\bin\HostX86\x86\cl.EXE"' : リターン コード '0x2'
Stop.

うまく include のパスが追加できていないみたいです。こういうときは、コンパイルしているビット数にあわせて、次のフォルダーをプロジェクトに追加します。

  • C:\openssl_x86\include
  • C:\openssl_x64\include

f:id:shikaku_sh:20200214144526p:plain:h300
Visual Studio のソリューション エクスプローラーにコピペした

Visual Studio にコピー&ペーストしてやると、こんな感じになりました。この状態でもう一度 nmake してみてください。

コンパイルできた

初回の成功をしたときは、こんな感じのメッセージでした。

f:id:shikaku_sh:20200214144656p:plain:w500
できた

Visual Studio のソリューションエクスプローラーからは、(プロジェクトに追加していないので)コンパイルして生成したファイルは確認できません。Windowsエクスプローラーで表示してみると、目的のファイルが生成されていました。

f:id:shikaku_sh:20200214144725p:plain:w500
ほしかったのは、これ

  • sqlcipher.dll
  • sqlite3.c

sqlite3.c は Android 用のコンパイルにも再利用できます。コードの互換性を保つ意味でも重要かも。

SQLiteUnityKit で使うためには

sqlite3.dll から sqlcipher.dll に Assets を交換します。SqliteDatabase.csDllImport 属性で関数を定義している部分があるので、libsqlcipher から呼び出すように修正します。

前に libsqliteX の .dll を使うパターンでポイントをメモした記事「Sqlitedatabase.cs の変更箇所」も参考になると思います。

// sqlite3_open を libsqlcipher.dll から呼び出す
[DllImport("libsqlcipher", EntryPoint = "sqlite3_open")]
// 他の関数も全部修正

// 以下の暗号化用の関数を新しく2つ追加する
[DllImport("libsqlcipher", EntryPoint = "sqlite3_key")]
private static extern int sqlite3_key(IntPtr stmHandle, string key, int len);

[DllImport("libsqlcipher", EntryPoint = "sqlite3_rekey")]
private static extern int sqlite3_rekey(IntPtr stmHandle, string key, int len);

これで x86/x64 用の libsqlcipher.dll を用意して Unity の Assets に追加・設定すれば OK でした。

暗号化に対応しているかチェックする要点

定義されている関数を DUMPBIN オプションでチェックします。

以下は、「sqlcipher.dll.dump」ファイルに DLL の内容を書き出しました。

dumpbin /exports sqlcipher.dll /OUT:sqlcipher.dll.dump

ファイルをメモ帳などで開いて、sqlite3_key で検索します。

f:id:shikaku_sh:20200214144946p:plain:w400
あるー

うまく DLL に目的の関数が追加されてそうですね。(通常の sqlite3.dll には「SQLite - List Of Functions」のように sqlite3_key が定義にありません)

使い方のポイント

暗号化のポイントは簡単です。

まとめると、以下のように sqlite3_open を呼び出したあとは、最初に sqlite3_key を呼び出してください。

初回の動作

  1. DB ファイルが存在しない状態で sqlite3_open
  2. sqlite3_key でパスワードを設定
  3. sqlite3_close で暗号化ファイルを生成

2回目以降の動作

  1. DB ファイルが存在する状態で sqlite3_open
  2. sqlite3_key でパスワードを設定
  3. SELECT 文などを実行する
  4. sqlite3_close で閉じる
public void Sample()
{
    var path = pathDB;
    IntPtr stmHandle;

    if (sqlite3_open(path + "", out _connection) != SQLITE_OK)
    {
        throw new SqliteException("Could not open database file: " + path);
    }

    var result = sqlite3_key(_connection, "password", "password".Length);
    
    ExecuteQueryExec("BEGIN");
    sqlite3_exec(_connection, "CREATE TABLE charactors ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, data1 INTEGER, data2 TEXT)", IntPtr.Zero, IntPtr.Zero, out stmHandle);
    sqlite3_exec(_connection, "INSERT INTO charactors (name, data1, data2) VALUES ('魔想志津香', 200, '火爆破')", IntPtr.Zero, IntPtr.Zero, out stmHandle);
    ExecuteQueryExec("COMMIT");

    sqlite3_close(_connection);
}

暗号化の注意点は、いまのところ暗号化したファイルを開くことができる DB ブラウザーがなさそうです。「SQLiteStudio」はできそうな気もするのですが、(いまのところ)うまく設定できないでいます。(できるなら教えてほしいです)

このあたりは以下が実例として参考になりそうです。

参考

暗号技術入門 第3版

暗号技術入門 第3版

  • 作者:結城 浩
  • 発売日: 2015/08/26
  • メディア: 単行本

WHY BLOCKCHAIN なぜ、ブロックチェーンなのか?

WHY BLOCKCHAIN なぜ、ブロックチェーンなのか?

  • 作者:坪井 大輔
  • 発売日: 2019/07/12
  • メディア: 単行本(ソフトカバー)