2011年12月20日火曜日

SQLite暗号化OSS「SQLCipher for Android」を使ってみた。

こんにちは@katsummyです。

※Twitterとデベロッパー名が違いますのであしからず^^


Android Advent Calendar 2011 の参加への投稿となります。
Android Advent Calenderについてはこちらを参照下さい。


勢いで参加したものの、先の方々がガチな技術や面白いネタの投稿をされてるのを見て恐縮してます。
。。゛(ノ><)ノ ヒィ
初心者向けな内容です。過度の期待はしないで下さい。


ネタは敷居高そうだし、技術的な投稿の内容を考えておりましたが、
あるところでAndroidの端末内のデータの暗号化について話題が上がったので、
その際に考えてた内容とSQLiteの暗号化について記載したいと思います。



Android 3.x 以降では、ストレージ暗号化機能を使用することができます。
SDカードや内蔵ストレージをすべて暗号化できますので安全性は高くなります。
その端末が専用に使用しているのであれば、有効では無いかと思います。しかし、他の用途でも使用している場合、ひとつのアプリのためだけに、全体を暗号化するのはどうかなと思います。
そもそも、Android 2.3.x 以前の端末では使用できません。


アプリ個別に管理しているファイルを暗号化する。
端末レベルで暗号化するより、比較的導入が容易かと思います。

Androidで、データを管理する方法としては、
・ファイル (.TXT .CSV .XML etc...)
プリファレンス(XML)
・SQLite
これらのファイルを暗号化します。

ファイルの暗号化は、AES/DES/RSA暗号などを用いれば良いと思います。コードサンプルはググれば多く見つかると思うので記載はか割愛します。
プリファレンス(XML)は、それ自信は暗号化されませんので、中に持つ値を暗号化する必要があります。

SQLiteは、暗号化機能を持っていないため、中のレコード自体は暗号化されていません。バイナリー化もされていない(?)ようなので、ダンプをとると容易に中身を参照することができてしまいます。レコードに格納する値を暗号化する方法もありますが、SQLのクエリーやソートが使用できなくなってしまいます。

SQLiteを暗号化するオープンソース・ソフトウェアにSQLCipherがあります。
CやPHP、iPhoneで使用することができてましたが、Android版も11月末に正式版?( v1 FINAL)がリリースされました。





使用するにはgithubからダウンロードしましすが、ソースコードが不要の場合、
https://github.com/guardianproject/android-database-sqlcipher/tree/master/dist/SQLCipherForAndroid-SDK
からダウンロードすれば良いと思います。
必要な


SQLCipherをプロジェクトに設定する手順


1) libs フォルダをプロジェクトにコピーします。
 - commons-codec.jar
 - guava-r09.jar
 - sqlcipher.jar
 + armeabi
  - libdatabase_sqlcipher.so
  - libsqlcipher_android.so
  - libstlport_shared.so


 ※ ターゲットが2.2以下の場合、 assets フォルダもプロジェクトにコピーします。
 + assets
  - icudt44l.zip


2) ソースコードの修正
 a) importするパッケージはSQLiteをSQLCipherに変更します。
  import android.database 
  -->>
  import info.guardianproject.database


 b) 初期処理としてライブラリのロードを行います。
  SQLiteDatabase.loadLibs(context);


 c) データベースをオープンする時にパスワードを指定します。
  SQLiteOpenHelper.getWritableDatabase("thisismysecret"):

  SQLiteOpenHelper.getReadableDatabase("thisismysecret"):


 後は、SQLiteと同じ記述となります。


サンプルソース
import android.database.Cursor; import info.guardianproject.database.sqlcipher.SQLiteDatabase; import android.app.Activity; import android.content.ContentValues; import android.os.Bundle; import android.util.Log; public class SQLDemoActivity extends Activity { EventDataSQLHelper mSQLHelper; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // SQLCipherライブラリのイニシャルロード SQLiteDatabase.loadLibs(this); String password = "foo123"; mSQLHelper = new EventDataSQLHelper(this); // パスワードを指定して書き込み可能なデータベースを開く SQLiteDatabase db = mSQLHelper.getWritableDatabase(password); // レコードのインサート for (int i = 1; i < 10; i++) { addEvent("こんにちは Android イベント: " + i, db); } db.close(); // パスワードを指定して読み込み可能なデータベースを開く db = mSQLHelper.getReadableDatabase(password); // クエリー Cursor cursor = db.query(EventDataSQLHelper.TABLE, null, null, null, null, null, null); startManagingCursor(cursor); // レコードの読み込み StringBuilder ret = new StringBuilder("Saved Events:\n\n"); while (cursor.moveToNext()) { long id = cursor.getLong(0); long time = cursor.getLong(1); String title = cursor.getString(2); ret.append(id + ": " + time + ": " + title + "\n"); } Log.i("sqldemo", ret.toString()); db.close(); } @Override public void onDestroy() { super.onDestroy(); mSQLHelper.close(); } private void addEvent(String title, SQLiteDatabase db) { ContentValues values = new ContentValues(); values.put(EventDataSQLHelper.TIME, System.currentTimeMillis()); values.put(EventDataSQLHelper.TITLE, title); db.insert(EventDataSQLHelper.TABLE, null, values); } }
import info.guardianproject.database.sqlcipher.SQLiteDatabase; import info.guardianproject.database.sqlcipher.SQLiteOpenHelper; import android.content.Context; import android.provider.BaseColumns; import android.util.Log; // SQLiteOpenHelper public class EventDataSQLHelper extends SQLiteOpenHelper { // データベス名 private static final String DATABASE_NAME = "events.db"; private static final int DATABASE_VERSION = 1; // テーブル名 public static final String TABLE = "events"; // カラム public static final String TIME = "time"; public static final String TITLE = "title"; // コンストラクタ public EventDataSQLHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { String sql = "create table " + TABLE + "( " + BaseColumns._ID + " integer primary key autoincrement, " + TIME + " integer, " + TITLE + " text not null);"; Log.d("EventsData", "onCreate: " + sql); db.execSQL(sql); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } }


ダンプ
$ hexdump -C events.db
  • SQLite
00000000  53 51 4c 69 74 65 20 66  6f 72 6d 61 74 20 33 00  |SQLite format 3.|
...
00000e30  00 00 00 00 00 31 09 04  00 05 5b 01 34 42 1d 75  |.....1....[.4B.u|
00000e40  20 e3 81 93 e3 82 93 e3  81 ab e3 81 a1 e3 81 af  | ...............|
00000e50  20 41 6e 64 72 6f 69 64  20 e3 82 a4 e3 83 99 e3  | Android .......|
00000e60  83 b3 e3 83 88 3a 20 39  31 08 04 00 05 5b 01 34  |.....: 91....[.4|
00000e70  42 1d 74 ec e3 81 93 e3  82 93 e3 81 ab e3 81 a1  |B.t.............|
00000e80  e3 81 af 20 41 6e 64 72  6f 69 64 20 e3 82 a4 e3  |... Android ....|
00000e90  83 99 e3 83 b3 e3 83 88  3a 20 38 31 07 04 00 05  |........: 81....|
00000ea0  5b 01 34 42 1d 74 d4 e3  81 93 e3 82 93 e3 81 ab  |[.4B.t..........|
00000eb0  e3 81 a1 e3 81 af 20 41  6e 64 72 6f 69 64 20 e3  |...... Android .|
…
  • SQLCipher
00000000  5a f7 b6 fa 3e 78 f2 9b  10 71 bc cd 64 e8 f4 01  |Z...>x...q..d...|
00000010  11 7c 48 1b 3f d9 15 e3  70 01 68 c0 d0 7c be 0b  |.|H.?...p.h..|..|
00000020  af 55 b5 95 7b a3 d2 83  90 61 31 0a 83 ce 52 13  |.U..{....a1...R.|
00000030  0a ac 02 a3 39 ad ec e2  92 65 3f 01 c6 d6 5d 97  |....9....e?...].|
00000040  b0 d0 f3 a3 5a c2 3b 1d  58 b4 d2 41 2a d0 bf 39  |....Z.;.X..A*..9|
00000050  3c 96 88 8b 66 ff d1 de  73 c5 c8 2e d6 52 6b 7d  |<...f...s....Rk}|
00000060  6c 7e f9 41 bb 9e 00 bb  3e d6 2e 9a dc 07 da 0f  |l~.A....>.......|
00000070  fe 0e 14 38 63 34 01 3e  e7 70 c4 4c 20 da c5 d5  |...8c4.>.p.L ...|
…
暗号化されているのが分かると思います。

なお、サンプルコードは、パスワードをソースコードに記述してますが、
端末を識別する情報やGoogleアカウント情報など一意となる情報から、
生成した方が良いと思います。

※ IMEI(端末識別番号)、IMSI(加入者識別番号)、ICCID(SIMカード固有番号)、Android IDの文字列を組み合わせて、ハッシュ化するとか〜。


SQLiteのデータベースは通常/data/data/package/databasesに作成されるためアプリ以外のユーザ権限で取得することはできませんがルートユーザでは可能になります。
また、データベースの保管先は、SDCardに指定することも可能です。


Rooted端末対策として、保管をSDCard指定する際には、SQLCipherを利用してみてはどうかなと思います。



ライセンス
同梱されているLICENSEファイルには、Apache License Version 2.0とされていました。
別にSQLCIPHER_LICENSEファイルにも再配布や免責事項に付いて記載がありますので確認しておいた方がよいと思います。






でわでわ。