APIキーなどをアプリ内でセキュアに保管する


Androidアプリで、第三者に読まれたくない文字列を、割と安全に持たせる方法です。
簡単でオススメです。たぶん。
ちなみに筆者はプログラミング一年目の初心者です。
これがどの程度安全なのかはわかりません。どなたかご教示願います。

今回はアプリに固有の文字列の話です。
各ユーザごとのID, Passwordの保管の話じゃなくて、出荷時に持たせておきたい文字列の話です。

## 文字列保管の選択肢

1. 使用するクラス内で変数保持して参照
- BuildConfigなどにstaticに持たせて参照
- res/values/string.xmlからgetString
- C, C++で文字列をreturn

ってな具合の選択肢があるかと思います。
たぶん。

で、ぶっちゃけ1~3て全然安全じゃないでしょ。
逆コンパイルで簡単に読めちゃうもん。

apk解析するのにいちいちコマンドライン叩くの面倒な人は、ここから逆コンパイルできます。
http://www.decompileandroid.com/

## だからといってネイティブで書くのもめんどい

だってわかんないし。CとかC++とか言われても。初心者だもん。
Android.mkだのAppication.mkだのヘッダーファイルだの。

だけどとりあえずは逆コンパで簡単に読まれないくらいの安全性は欲しい！
はい、これ
https://github.com/shamanland/simple-string-obfuscator

使い方は簡単です。
リンク先読めばわかるんだけど、どのくらい簡単かっていうと、

- .shファイルをソースに保管したい文字列に対して実行
- コピーしてソースに貼り付け

はい。これだけ。
こんな感じ。

```bash
$ ./obfuscate_string.sh  I\'m from MyApplication\#getSecureKey
```

```bash:実行結果
(new Object() {int t;public String toString() {byte[] buf = new byte[3];t = 796543738;buf[0] = (byte) (t >>> 11);t = -660440049;buf[1] = (byte) (t >>> 12);t = -748069602;buf[2] = (byte) (t >>> 19);return new String(buf);}}.toString())
```

これが何をしているのかはよくわかんないけど笑
バイトコードにしてどうのこうの的なことでしょ、たぶん
だれか教えてくれると嬉しいです。
## で、どうなんの？

これが書いたコード。

```java:MainActivity.class
public class MainActivity extends AppCompatActivity {

    private static final String NAKED_STRING = "I'm from static String of MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);;
        setSupportActionBar(toolbar);

        // 今回はMyApplicationで定数管理するから、それを取得
        MyApplication app = (MyApplication) getApplication();
        // static final でクラス内部に保持した文字列を参照
        ((TextView) findViewById(R.id.text_static)).setText(STATIC_VARIABLE_STRING);
        // strings.xmlから文字列取得
        ((TextView) findViewById(R.id.text_xml)).setText((getString(R.string.from_string)));
        // MyApplicationから変数保持していない裸の文字列をget
        ((TextView) findViewById(R.id.text_naked)).setText(app.getNakedKey());
        // MyApplicationから、さっきの実行結果を使ってget
        ((TextView) findViewById(R.id.text_secure)).setText(app.getSecureKey());
    }
}
```

```java:MyApplication.class
public class MyApplication extends Application {

    public String getNakedKey() {
        return "I'm from MyApplication#getNakedKey";
    }

    public String getSecureKey() {
    
    	 // これが、さっきターミナル叩いて得た実行結果
        return (new Object() {int t;public String toString()
        {byte[] buf = new byte[3];t = -1993940741;
            buf[0] = (byte) (t >>> 18);t = -2092071699;
            buf[1] = (byte) (t >>> 5);t = 765513572;
            buf[2] = (byte) (t >>> 21);
            return new String(buf);}}.toString());
    }
}

```

キャプチャ
![resize.png](https://qiita-image-store.s3.amazonaws.com/0/75088/a58b5ab5-4d35-a7b0-d408-6993b87c44f9.png "resize.png")


上から、

1. クラス内部に変数保持した文字列
- strings.xmlからgetStringした文字列
- MyApplication.classから取ってきた、変数保持していない文字列
- MyApplication.classから、さっきの実行結果でつくったメソッドを使って取ってきた文字列

で、逆コンパイルしたMainActivity
変数保持した文字列は余裕で読める。

![e60489082b36d07f4d8b5ee8179643f5.png](https://qiita-image-store.s3.amazonaws.com/0/75088/454ee2da-39a7-9be3-353c-40b5f4ea383c.png "e60489082b36d07f4d8b5ee8179643f5.png")


同じくMyApplication#getNakedString
これは変数保持してないけど、違いなし。

![56b85f7653fec6e5547325d0981216e3.png](https://qiita-image-store.s3.amazonaws.com/0/75088/f9b74207-55ca-272f-2104-0bcec709a2ff.png "56b85f7653fec6e5547325d0981216e3.png")


で、res/values/string.xmlも載せようと思ったんだけど、今回取り出せなかった。
apktoolだかなんだか使えばできた気がするんだけど、さっきのwebサービスだと出力結果に含まれなかった。
けど、絶対できるから！string.xmlに持たせるのもなしね！
そうしないとこれ書いた意味ないから！

で、肝心のMyApplication#getSecureString

![623f0a811907272d9a7752eb255e1da5.png](https://qiita-image-store.s3.amazonaws.com/0/75088/9958a90f-6594-5794-670d-04c97addd93b.png "623f0a811907272d9a7752eb255e1da5.png")

はい、何書いてあるかぜんぜんわかんない。
って思って安心してたら、なんかこんなのもあった。

![37b76948d1c9e565a3830808f65e0f8e.png](https://qiita-image-store.s3.amazonaws.com/0/75088/c2dd9010-699c-5f9c-176d-2e28515d1f2e.png "37b76948d1c9e565a3830808f65e0f8e.png")

これだとなんかいかにも読まれたくないですって顔してて、復元されそうだけど、他の方法よりずっとマシでしょ。

## まとめ

コスパ的にはこの方法が一番いいんじゃないんでしょうか。
一番安全なのは、ネイティブで書くことなんだろうけど、AndroidNDKを使うと、ビルドの設定からやりなおさないといけないっぽいし。たぶんね。
もっといい方法あったら教えてください。