みなさまこんにちは。再びデータセキュリティについての記事になります。

前回のiOSアプリにおけるデータセキュリティを考えるに続いて今回はAndroidにおけるデータセキュリティに関して施策と機能、情報処理推進機構(IPA)発のAndroidの脆弱性学習・点検ツールであるAnCoLeについてもご紹介したいと思います。

パスワードやトークン等の扱い

一般にパスワードベースの認証を行っているサービスでは、再びパスワードを入力しなくていいようにパスワードを保存しておくことや、OAuth等でトークンを保存しておくことがあると思います。

そこでまず考えたいことは極めてセンシティブな情報であるパスワードを保存しなくて良いのなら保存しない方がいいということです。 もしも本当にパスワードを保存する必要があるのであれば、パスワードの暗号化、暗号化鍵の取り扱い方等の対策が必要になってきます。

あるいはトークンをパスワードの代わりに使えるのであれば、トークンを使用します。トークンは一般的にパスワードよりも有効期限が短く、いつでも無効化できるのでパスワードよりも安全だとされています。

データの保存とアクセス権限

以下の様な方法を使用して重要なデータを保存した場合、基本的にはアプリ自身からしかアクセスできないような非公開な状態にします。 ただし非公開に指定したとしてもroot権限を持った端末ではアクセス可能になってしまうので、大事なデータには暗号化等の処理をおこないます。

Shared Preferences

明示的にMODE_PRIVATEを指定し、自身のアプリからしかアクセスできないようにします。

SharedPreferences preference = getSharedPreferences("hogehoge", MODE_PRIVATE);
SQLite
  • SQLiteDatabase#openOrCreateDatabaseは機種によっては自分自身以外のアプリからも読み取り可能なファイルが作成されることがあるので、基本的には使用しないほうがいいです。

  • Context#openOrCreateDatabaseの使用

明示的にMODE_PRIVATEを指定し、自身のアプリからしかアクセスできないようにします。

db = Context.openOrCreateDatabase("Sample.db", MODE_PRIVATE, null);
  • SQLiteOpenHelperの使用

SQLiteOpenHelperを継承したサブクラスを作成し、コンストラクタにデータベースの名前を渡すことでプライベート権限のファイルを作成することができます。

public class SampleSQLiteOpenHelper extends SQLiteOpenHelper{
    public SampleSQLiteOpenHelper(Context context){
        super(context, DB_NAME, null, DB_VERSION);
    }

    //省略...
}
独自のファイル

アプリディレクトリ内に作成し、アクセス権をプライベートに指定することで非公開ファイルにします。

FileOutputStream fos = openFileOutput("hogehoge", MODE_PRIVATE);
Account Manager

Account ManagerはOS側で様々なWebサービスのアカウントを一元管理する仕組みです。

Account Managerではパスワードと認証トークンの2つの認証情報を保存することができますが、これらの情報は平文で保存されます。 そのため特にパスワードの保存はしないことが推奨されます。

もしパスワードを保存したいのであれば、暗号化などの対策を施した上で保存することをオススメします。

データの暗号化


Javaの暗号アーキテクチャJCAによる暗号化

Javaに用意されているJCA(Java Cryptography Architecture)というフレームワークはAndroid上で利用することができ、様々な暗号方式をサポートしています。 ここでは共通鍵暗号方式であるAES暗号の例をご紹介します。

ではさっそく秘密鍵を用意します。SecretKeySpecはbyteの配列から秘密鍵を作成しますので、文字列から作成する場合は以下の例のようにします。 また、今回はAESを用いますのでアルゴリズムとしてAESを指定します。

String secretKeyStr = "0123456789ABCDEF"
SecretKeySpec key = new SecretKeySpec(secretKeyStr.getBytes(), "AES");

先ほど用意した鍵で暗号化します。

暗号化するためにはCipherオブジェクトを取得し、暗号化モードへの設定と使用する鍵の設定を行います。

オブジェクトの取得の際にはどのような暗号を使用するかの設定が必要になります。今回はAES/CBC/PKCS5Paddingのように暗号モードとパディングを指定していますが、単にAESとすることも可能です。ただし、単にAESとした場合には暗号モードとパディングがAndroidが利用可能なデフォルトのものになります。このデフォルトの設定はあまり安全でない場合があるため、基本的にはAES/CBC/PKCS5Paddingのように暗号モードとパディングを明示的に設定します。

設定が終わった後はdoFinalメソッドによって暗号化します。

//暗号化アルゴリズム、動作モード、パディングを指定してCipherオブジェクトの取得
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
//暗号化モード、秘密鍵を設定
cipher.init(Cipher.ENCRYPT_MODE, key);
//暗号化
encryptedData = cipher.doFinal(dataStr.getBytes());
//IVの保存
IvParameterSpec ips = new IvParameterSpec(cipher.getIV());

先ほど暗号化したデータを復号したいと思います。

復号のためには暗号化のときと同様にCipherオブジェクトを取得します。ここでは暗号化の際に設定したものと同じ暗号を指定します。

次に設定を行いますが、今回は暗号化と少し異なります。必要なのは復号モードへの設定と使用する鍵の設定、さらにIV(初期化ベクトル)の設定を行います。暗号化の際にIvParameterSpecとしてgetIVメソッドを用いてIVを保存していますが、このIVが必要になります。ただし暗号モード等によっては不要な場合もあるのでご注意下さい。

設定が終われば、後は暗号化されたデータをdoFinalメソッドによって復号します。

//暗号化と同様にCipherオブジェクトの取得
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
//復号モードで秘密鍵とIVを設定
cipher.init(Cipher.DECRYPT_MODE, key, ips);
//復号
byte[] decryptedData = cipher.doFinal(encryptedData);

以上AESによる暗号化・復号の例でした。

このようにしてJavaに用意されているJCAを利用することによって、独自実装ではなく既存のアルゴリズムを利用してデータの暗号化・復号をすることができます。

SQLCipher によるSQLiteの暗号化

iOSアプリにおけるデータセキュリティを考えるで紹介した、透過的にデーターベースをAESで暗号化してくれるSQLiteの拡張SQLCipherAndroid版の紹介です。

なお、今回の説明はAndroid Studio向けとなっています。(2015/4時点でv3.3.0)

導入についてですが、まずこちらからAndroid向けのライブラリをダウンロードし展開します。

次にSQLCipherを利用したいプロジェクトのsrc -> main内にassetsディレクトリを作成します。作成したらライブラリのassets内のicudt46l.zipをこのディレクトリ内にコピーします。

次にライブラリのlibs内のsqlcipher-javadoc.jarsqlcipher.jarをプロジェクト内のlibsにコピーします。

最後にsrc -> main内にjniLibsディレクトリを作成します。作成したらライブラリのlibs内のarmeabiarmeabi-v7ax86をこのディレクトリにコピーします。

以上で導入は完了です。導入を終えると以下のようなファイルの配置になっているかと思います。

導入完了後のプロジェクトツリー

ここまで終えたら公式のサンプルコードを参考に動作を確認してみます。

//省略...
import net.sqlcipher.database.SQLiteDatabase;

public class SQLCipherTestActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        InitializeSQLCipher(); //初期化処理
    }

    //test123を鍵とする
    private void InitializeSQLCipher() {
        SQLiteDatabase.loadLibs(this);
        File databaseFile = getDatabasePath("demo.db");
        databaseFile.mkdirs();
        databaseFile.delete();
        SQLiteDatabase database = SQLiteDatabase.openOrCreateDatabase(databaseFile, "test123", null);
        database.execSQL("create table t1(a, b)");
        database.execSQL("insert into t1(a, b) values(?, ?)", new Object[]{"one for the money",
                "two for the show"});
    }

    //省略...
}       

実行して作成されたをSQLiteのファイルをビューワで見てみようとすると開けないと思います。また、以下のようにバイナリエディタでdb開いてみても通常のSQLiteのファイルとは違い中身が暗号化されていることがわかると思います。

バイナリエディタの画像

鍵の保管の問題

大事なデータを守るために暗号化を行った際に浮上する問題は、秘密鍵をどのように管理するかです。せっかくデータを暗号化しようとも鍵がばれてしまうと元も子もありません。

ですが、結論から言ってしまうと端末内での鍵管理を行う以上は現状絶対安全だと言い切れる方法はないように思えます。 そのため大事なデータはそもそも端末内には保管しないで済むようなアプリの設計を行うことも大事なことかもしれません。

対処として鍵を分散して保存したり外部サーバ上に鍵を置いておくなども考えられますが、このような形態をとるのが難しい場合も有ります。 そこで端末内での鍵管理の役に立つかもしれない機能についてご紹介したいと思います。

Android KeyStore Provider

ここではAndroid4.3でサポートされた Android KeyStore Providerについて簡単にご紹介したいと思います。

このAndroid Key Store Providerでは自身のアプリだけで利用が可能な鍵を生成・保持することができます。

鍵の生成から見て行きたいと思います。鍵の生成にはKeyPairGeneratorKeyPairGeneratorSpecを使用します。

KeyPairGenerator.getInstance()において、プロバイダにAndroidKeyStoreを指定するのを忘れないでください。

Calendar cal = Calendar.getInstance();
Date now = cal.getTime();
cal.add(Calendar.YEAR, 1);
Date end = cal.getTime();

KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
kpg.initialize(new KeyPairGeneratorSpec.Builder(getActivity().getApplicationContext())
    .setAlias(alias)
    .setStartDate(now)
    .setEndDate(end)
    .setSerialNumber(BigInteger.valueOf(1))
    .setSubject(new X500Principal("CN=test1"))
    .build());  

KeyPair kp = kpg.generateKeyPair();

続いて鍵を取り出してみます。Android KeyStoreをロードするために、KeyStore.getInstance("AndroidKeyStore")を呼び出しています。

KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
KeyStore.Entry entry = ks.getEntry(alias, null);
PrivateKey privateKey = ((KeyStore.PrivateKeyEntry) entry).getPrivateKey();

公式に細かい説明やサンプルコードがあるので、こちらのほうもぜひご覧ください。

Hardware credential storage

Hardware Credential StorageAndroid4.3でサポートされた機能で、 ハードウェア側で安全が保証された領域に鍵を作成・格納をしてくれます。この領域はKernelからもデバイスの外に取り出すことができないプライベートな領域になっています。

なかなか便利そうなのですが、全てのAndroid端末がサポートしているわけではないためhardware credential storageを利用する場合は、KeyChain.IsBoundKeyAlgorithm()で使用できるかどうかのチェックを行う必要があります。

おまけ – AnCoLeを使ってみる

みなさん情報処理推進機構(IPA)がAndroidアプリのセキュリティ学習・点検ツール「AnCoLe」を公開しているのをご存じですか?。 このAnCoLeは無償で公開されており、Eclipse上でIPAに多く届けられた7つの脆弱性について学習・点検の両方が行えるツールです。

前述の7つの脆弱性は以下のとおりです。

  • ファイルアクセスの不備
  • コンポーネントのアクセス制限不備
  • 暗黙的Intentの不適切な使用
  • 不適切なログ出力
  • WebViewの不適切な使用
  • SSL通信の実装不備
  • 不必要な権限の取得
導入

まずは導入からです。IPAのAnCoLeのインストールと起動手順の通りに行います。

まずはEclipseを起動している場合はEclipseを終了します。

次にAnCoLeのダウンロードページからancole.zipをダウンロードし、展開します。 展開されたファイルでjp.go.ipa.android.security.learningで始まるjarファイルをEclipseのインストールディレクトリ直下のdropinsディレクトリにコピーします。

Eclipseを改めて起動してみてツールバー上に鉛筆虫眼鏡のボタンが表示されていればインストールの完了です。

使ってみる

ここでは簡単に使い方を紹介します。 詳しく知りたい方はダウンロードしたancole.zipに「AnCoLe利用者マニュアル」が含まれているので、そちらをご覧ください。

学習機能

学習機能はAnCoLeが提供するシナリオを通じて注意すべき脆弱性の情報や、対策方法についての学習をすることができます。
シナリオそれぞれにはサンプルとして脆弱性をもつアプリとその脆弱性を悪用する攻撃アプリが含まれています。

学習機能は先ほどのインストールで追加された鉛筆のボタンをクリックすることで学習を開始することができます。ボタンをクリックすると以下の様な画面になります。 後は左側の一覧から学習したい項目を選択し、学習を進めることができます。

点検機能

点検機能はアプリのプロジェクトに脆弱性がないかを点検することができます。検出された脆弱性に対する対策の学習や、直接該当箇所を含むソースコードを呼び出して修正することもできます。

点検機能は虫眼鏡のボタンをクリックすることで利用が可能です。虫眼鏡のボタンをクリックすると、以下のように点検を行いたいプロジェクトの選択画面が出てくるので、 選択してOKを押すことで点検が始まります。

OKを押すと自動的に点検を行い、結果を以下のように表示してくれます。見つかった脆弱性に対しては学習機能を使って該当の脆弱性に対する学習を行うことができ、さらには脆弱性の存在する該当箇所を教えてくれたりもします。

いかがだったでしょうか?Androidにおける脆弱性について学習ができ、さらには脆弱性の点検までできるAnCoLe。 ぜひ実際に導入して学習し、自分のつくったアプリの脆弱性診断にも使ってみましょう!

ところが残念ながら現在(2015/4時点)ではEclipseのみの対応です。 GoogleがEclipseサポートを将来的に終了することを表明していますし、ぜひAndroid Studioにも対応して欲しいですね!

参考資料