2012/03/11

Android:セキュリティの確保と耐タンパー性

Androidのセキュリティは話題に事欠きません。
Android端末は基本的にユーザ権限で動作するように作られています(最小権限の原理です)
しかし、Androidの脆弱性をついてroot権限を奪取する攻撃が存在します。
既に発売されている端末でもこれは同様で、今現在のAndroidはroot権限の奪取を防ぐこと
ができていません。

このことから、アプリ開発においては「root権限が奪取された環境で動作する」あるいは、
「アプリは解析される」という前提で、アプリの耐タンパー性を考える必要があります。

下記はデータをAESの方式で暗号化する簡単なサンプルです。
private static final String SECRET_KEY_SRC = "ABCDEFGHIJKLMNOP";
private static final String SECRET_IV_SRC = "QRSTUVWXYZabcdef";

@Override
protected void onResume() {
    super.onResume();

    String target = System.getProperty("yuki.target", "test");
    Logger.d("Target=" + target);
    byte[] encrypted = encrypt(target.getBytes());
    Logger.d("Encrypt=" + new String(encrypted));
    byte[] decrypted = decrypt(encrypted);
    Logger.d("Decrypt=" + new String(decrypted));
}

private byte[] encrypt(byte[] target) {
    if (target == null || target.length <= 0) {
        return new byte[0];
    }

    // create SecretKey
    byte[] keySrc = SECRET_KEY_SRC.getBytes();
    SecretKeySpec key = new SecretKeySpec(keySrc, "AES");

    // create IV
    byte[] ivSrc = SECRET_IV_SRC.getBytes();
    IvParameterSpec iv = new IvParameterSpec(ivSrc);

    try {
        Cipher encrypt = Cipher.getInstance("AES/CBC/PKCS5Padding");
        encrypt.init(Cipher.ENCRYPT_MODE, key, iv);
        return encrypt.doFinal(target);
    } catch (Exception e) {
        throw new RuntimeException("Encrypt error.");
    }
}

private byte[] decrypt(byte[] target) {
    if (target == null || target.length <= 0) {
        return new byte[0];
    }

    // create SecretKey
    byte[] keySrc = SECRET_KEY_SRC.getBytes();
    SecretKeySpec key = new SecretKeySpec(keySrc, "AES");

    // create IV
    byte[] ivSrc = SECRET_IV_SRC.getBytes();
    IvParameterSpec iv = new IvParameterSpec(ivSrc);

    try {
        Cipher decrypt = Cipher.getInstance("AES/CBC/PKCS5Padding");
        decrypt.init(Cipher.DECRYPT_MODE, key, iv);
        return decrypt.doFinal(target);
    } catch (Exception e) {
        throw new RuntimeException("Encrypt error.");
    }
}

上記コードはセキュリティを十分に確保できるものでしょうか?この答えは一概には言えません。
上記のようなコードで十分なケースもあれば、全く暗号化の必要がないケースもあるからです。

耐タンパー性を考える上で意識しなければならないことについて、私なりの考えを本稿に
まとめました。
※本稿は完全な安全性を保証するものではありません。
※あくまでも参考として頂きますようお願いいたします。

0.耐タンパー化する前に...
耐タンパー化を実施する前に下記を熟考する必要があります
・そもそも耐タンパー化が必要か?
・耐タンパー化が必要な場合、求められる強度はどの程度か?

1つめは重要です。
悪意あるユーザにアプリのデータを抜き取られても全く問題のないアプリをつくるにはど
うすればよいのでしょうか?答えは非常にシンプルです。
「攻撃者にとって価値のある情報を持たなければよい」のです。
これについては思っている以上に検討の余地が残っているはずです。
アプリが必要以上にデータを持たなくて済む方法はないか?検討することが重要です。

2つめについては「あなたのアプリに必要なセキュリティ強度は?」
という質問に置き換えることもできます。
例えば、目覚まし時計のアラーム設定時刻が入ったDBとクレジットカード番号が入ったDB。
間違いなく後者の方が高いセキュリティを求められます。
このように、セキュリティ保護の対象となるデータによって求められるセキュリティ強度
は異なるのです。

あなたはアプリを攻撃する"悪意あるユーザ"の立場にあると仮定します。
目覚まし時計のアラーム設定時刻を盗むために端末をroot化してapkを抜き取り、逆コン
パイルした上で難読化されたコードを解析するでしょうか?おそらくしないでしょう。
データを盗むのにもコストが掛かります。データを盗んで得られるメリットよりデメリッ
トの方が大きいと攻撃される危険性は下がり、ソフトウェアは安全になります。
計算量的安全性の上に成り立っている暗号化の考え方も似たようなものです。

あなたはアプリを難読化するだけで十分に安全を担保できるのか?データの暗号化が必要
なのか?求められる強度はどの程度なのかを把握する必要があります。

また、耐タンパー化処理はアプリのパフォーマンスに影響することを忘れてはいけません。
いくらセキュリティを確保したとしても、パフォーマンスが劣悪では誰もそのアプリを欲
しいとは思わないからです。

これらを踏まえた上で耐タンパー化について考えていきます。


1.耐タンパー化:難読化(JavaとJNI)
以前投稿しましたが、Javaコードはapkさえ手に入れば逆コンパイルで抜き取ることが可
能です。可能であればJavaコード上で秘匿情報を扱わないのが得策です。
AndroidではJNI経由でネイティブ実装する仕組みがサポートされています。
ネイティブコードは機械語変換されるため、コード解析の難易度が飛躍的にあがります。


2.秘匿情報の寿命
ネイティブ側で秘匿情報を扱う際にも、メモリダンプには注意する必要があります。
鍵情報のようなデータを扱う場合、これをメモリ上に展開するとメモリダンプによって盗
まれる危険性があります。
そのため、秘匿情報をメモリ上に展開する時間は可能な限り短くするのが得策です。


3.名前への配慮
例えば「secret_key.bin」といった名前のファイルがあった場合を考えます。
このような名前は「これは秘密鍵です。盗むのであればこのファイルでしょう」
と攻撃者にヒントを与えているようなものです。

ファイル名だけではありません。コード上の変数名やメソッド・関数名、クラスファイル名、
データベースのフィールド名、"AES"といったような文字列も解読の手がかりとなります。
攻撃者へのヒントとなるような情報は可能な限り伏せた方が良いでしょう。
これらは難読化として知られ、いくつかはツールがサポートしてくれますが、ファイル名
などツールのサポート外となるものは、うっかり忘れがちです。


4.数字への配慮
128,256といった数字にも配慮しましょう。
「名前への配慮」と同じく解読の手がかりとなります。

アプリ解析でa, b, cの3ファイルが見つかり、どれか1つが鍵データであると仮定します。
それぞれのファイルサイズが
  • a - 40,000bit
  • b - 128bit
  • c - 8,000bit
である場合、ファイルbが鍵データである可能性が高くなります。
これは、鍵長に128bit, 160bit, 256bitがよく使用されるためです。

鍵データには余分なデータ(実行時には無視するデータ)を付加する等の工夫が必要です。


1~4は「攻撃者からデータを隠蔽し、解読を困難にする方法」であることがわかります。
暗号化といった高度なものから、相手の心理や行動をついたものまで耐タンパー化の方法
は様々です。
ここで、再び最初に示したコードを見ると様々な脆弱性が見えてくると思います。

自分のアプリに求められるセキュリティ強度を把握した上で、1~4それぞれの対策の
必要or不必要を判断、適用してセキュリティの向上に努めていくことが肝心です。

以上です。