Androidにおける機密情報の保持
ログイン機能のあるアプリを作成すると機密情報(個人情報やアクセストークン)を端末内に保持することがよくあると思います。
その場合データの暗号化を行いますが、Androidアプリにおいて悩みの種なのがその暗号化のための秘密鍵(共通鍵)の置き場所です。
Android(Java)はリバースエンジニアリングが容易なため、アプリ内に暗号化のための鍵をハードコーディングしておくのは色々とヤバいです。
定数で持っておく以上難読化しても意味をなさないため、複雑なロジックを介して鍵を取り出したりCのコードに埋め込んだりして読み取られにくくする手法がよく取られます。
が、必要十分なセキュリティが担保されてるか判断つかないしぶっちゃけめんどくさい
Android 4.3以降ならAndroid Keystore System使えるよ
Android4.3以降限定ですが、Android Keystore Systemという機能が追加され端末内の安全な領域(Credential Storage)に鍵を生成、保存することが出来るようになりました。
iOSのキーチェーンみたいなやつです。
アプリの署名に使うKeystoreと名前が被ってややこしいですが別物です。
暗号化
- Keystoreで鍵生成、保存
- 鍵を取り出してそれをキーに生データを暗号化
- 暗号化したデータをDBやPreferenceへ保存
復号化
- Keystoreから暗号化に使った鍵取得
- DBやPreferenceから暗号化データを取得
- 暗号化データを鍵を使って復号化
という手順を踏むことで安全にデータを保存できます
課題
- Root化された端末では無力
Root化された端末においてはCredential Storageを覗く事も可能なため万能ではないですが、少なくともadb backupでアプリとデータをぶっこ抜かれて解析される心配はなくなるはずです。
- Androidのバージョンの制限が厳しい
Android 4.3以上でないとそもそも動作せず、さらにAndroid 6.0未満だとRSAしか使えないという残念仕様(参考)。Keystore使うなら当分はRSAオンリーになりそう。
- 秘密鍵作るときなんか重い
下記のGithubのサンプルコードを動作させてみればわかりますが初回の鍵生成処理が重く、UIスレッドで行うと一瞬固まります。
必要に応じて非同期処理で処理させたほうがいいかもしれません。
- 公式ドキュメント読んだけどよくわからない。。。6.0向けの記述しかないし
ので、サンプルを兼ねて暗号化と鍵保存の処理ををまとめたラッパークラス作ってみました。
文字列の暗号化を前提としています。
モジュール分割してあるので(一応)外部ライブラリとしても動作します。使い方は以下の通り。
Cryptore
[2017/03/26追記]ライブラリ化しました。くわしくはこちらの記事を参照下さい
private Cryptore getCryptore() throws Exception {
Cryptore.Builder builder = new Cryptore.Builder();
builder.type(CipherAlgorithm.RSA);
// builder.blockMode(CipherProperties.BLOCK_MODE_ECB); // If Needed.
// builder.encryptionPadding(CipherProperties.ENCRYPTION_PADDING_RSA_PKCS1); // If Needed.
builder.context(this); // Need Only RSA on below API Lv22.
builder.alias("ALIAS");
return builder.build();
}
BuilderパターンでCryptoreインスタンスを取得します。今回はRSA暗号を指定しています。
パラメータは何も指定していなければECB Block Mode + PKCS1Paddingが適用されます。
上記の"ALIAS"
というのが鍵取得のための引換券みたいなものなので、暗号化と復号化の際は同様の文字列を指定して下さい。("ALIAS"
自体は機密情報じゃないです)
private String encrypt(String originalStr) {
String encryptedStr = null;
try {
Cryptore cryptore = getCryptore();
encryptedStr = cryptore.encryptString(originalStr);
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
}
return encryptedStr;
}
Cryptore#encryptString()
で暗号化を行います。エラーハンドリングは必要に応じて適当にやって下さい。復号化はCryptore#decryptString()
です。
暗号化のアルゴリズムや暗号化ブロックモード、パディングはBuilderで制御できるようにあるのでプロダクトの性質に合わせて変更して下さい。
あと簡略化のためライブラリ側で決め打ちしてしまっているパラメータも一部あるのでそこも必要に応じて直に変更して下さい。
参考
こちらの記事を参考にさせていただきました。
Android 4.3 で Android Keystore を使う