Android の暗号化 API
概要
"モバイルアプリの暗号化" の章では、一般的な暗号のベストプラクティスを紹介し、暗号が間違って使用される場合に起こりうる典型的な問題について説明しました。この章では、Android の暗号化 API について詳しく説明します。ソースコード内でのこれらの API の使用を特定する方法とその暗号設定を判断する方法を示します。コードをレビューする際には、使用されている暗号パラメータをこのガイドにリンクされている現行のベストプラクティスと比較するようにしてください。
Android 上の暗号化システムの主要コンポーネントを特定できます。
Android 暗号化 API は Java Cryptography Architecture (JCA) をベースとしています。JCA はインタフェースと実装を分離し、暗号化アルゴリズムのセットを実装できる複数の セキュリティプロバイダ を含めることを可能にしています。 JCA インタフェースのほとんどは java.security.*
および javax.crypto.*
パッケージで定義されています。さらに、 Android 固有のパッケージ android.security.*
および android.security.keystore.*
があります。
KeyStore および KeyChain は鍵を保存および使用するための API を提供しています (裏では、 KeyChain API は KeyStore システムを使用しています) 。これらのシステムは暗号鍵のライフサイクル全体を管理することを可能にします。暗号鍵管理を実装するための要件およびガイダンスは Key Management Cheat Sheet に記載されています。以下のフェーズが考えられます。
鍵の生成
鍵の使用
鍵の保管
鍵のアーカイブ
鍵の削除
鍵の保管については "データストレージのテスト" の章で解析していますのでご注意ください。
これらのフェーズは KeyStore/KeyChain システムにより管理されます。ただしシステムの動作はアプリケーション開発者の実装方法により異なります。解析プロセスではアプリケーション開発者が使用する機能に焦点を当てる必要があります。以下の機能を特定および検証する必要があります。
鍵ローテーション
最新の API レベルをターゲットとするアプリでは、以下の変更が行われました。
Android 7.0 (API level 24) 以上について Android 開発者ブログでは以下のように記しています 。
セキュリティプロバイダの指定を停止することを推奨します。代わりに、常にパッチされた セキュリティプロバイダ を使用します。
Crypto
プロバイダのサポートは中止されており、このプロバイダは非推奨です。同じことがセキュアランダムのためのSHA1PRNG
にも当てはまります。
Android 8.1 (API レベル 27) 以上について 開発者ドキュメント は以下のように記しています。
AndroidOpenSSL
として知られる Conscrypt は上述の Bouncy Castle を使用することをお勧めします。これは次の新しい実装を有します。AlgorithmParameters:GCM
,KeyGenerator:AES
,KeyGenerator:DESEDE
,KeyGenerator:HMACMD5
,KeyGenerator:HMACSHA1
,KeyGenerator:HMACSHA224
,KeyGenerator:HMACSHA256
,KeyGenerator:HMACSHA384
,KeyGenerator:HMACSHA512
,SecretKeyFactory:DESEDE
,Signature:NONEWITHECDSA
GCM にはもはや
IvParameterSpec.class
を使用すべきではありません。代わりにGCMParameterSpec.class
を使用します。ソケットは
OpenSSLSocketImpl
からConscryptFileDescriptorSocket
およびConscryptEngineSocket
に変更されています。ヌルパラメータを持つ
SSLSession
は NullPointerException を返します。鍵を生成するために入力バイトとして十分な大きさの配列を持つ必要があります。そうでない場合 InvalidKeySpecException がスローされます。
ソケット読み込みが中断された場合は
SocketException
を取得します。
Android 9 (API レベル 28) 以上について Android 開発者ブログ はさらに多くの変更を記しています。
getInstance
メソッドを使用してセキュリティプロバイダを指定し、 28 未満の API をターゲットにすると、警告が発生します。 Android 9 (API レベル 28) 以上をターゲットにした場合、エラーが発生します。Crypto
プロバイダは現在削除されています。これをコールするとNoSuchProviderException
が返されます。
Android 10 (API レベル 29) について 開発者ドキュメント にすべてのネットワークセキュリティの変更がリストされています。
一般的な改善方法
アプリ審査の際には以下の推奨事項リストを考慮する必要があります。
"モバイルアプリの暗号化" の章で説明されているベストプラクティスが守られていることを確認します。
セキュリティプロバイダが最新アップデートであることを確認します - セキュリティプロバイダの更新 。
セキュリティプロバイダの指定を停止し、デフォルト実装 (AndroidOpenSSL, Conscrypt) を使用します。
Crypto セキュリティプロバイダとその
SHA1PRNG
は非推奨であるため使用を停止します。Android KeyStore システムに対してのみセキュリティプロバイダを指定します。
IV なしでのパスワードベースの暗号化方式の使用を停止します。
KeyPairGeneratorSpec の代わりに KeyGenParameterSpec を使用します。
セキュリティプロバイダ
Android は Java Security サービスの実装を java.security.Provider
クラスに依存しています。これらのプロバイダはセキュアなネットワーク通信と、暗号に依存するその他のセキュアな機能を確保するために重要です。
Android に含まれるセキュリティプロバイダのリストは Android のバージョンや OEM 固有のビルドにより異なります。古いバージョンのセキュリティプロバイダの実装の中には安全性が低いものや脆弱性があるものが知られています。したがって、 Android アプリケーションは正しいアルゴリズムを選択して適切な構成を提供するだけでなく、場合によってはレガシーセキュリティプロバイダの実装の強度にも注意を払う必要があります。
以下のコードを使用して既存のセキュリティプロバイダのセットを一覧表示できます。
これは Google Play API を備えたエミュレータで実行中の Android 9 (API レベル 28) の出力です。
セキュリティプロバイダの更新
コンポーネントに最新のパッチを適用し続けることはセキュリティ原則の一つです。同じことが provider
にも当てはまります。アプリケーションは使用されているセキュリティプロバイダが最新かどうかを確認し、最新でない場合には 更新してください 。
旧バージョンの Android
古いバージョンの Android (例: Android 7.0 (API レベル 24) より以前のバージョンのみ使用) をサポートする一部のアプリケーションでは、最新のライブラリをバンドルすることが唯一の選択肢かもしれません。Conscrypt ライブラリはさまざまな API レベルで暗号化の一貫性を保ち、より重いライブラリである Bouncy Castle をインポートする必要がないようにするため、この状況では適切な選択といえます。
Conscrypt for Android は以下の方法でインポートできます。
次に、以下を呼び出してプロバイダを登録する必要があります。
鍵生成
Android SDK は鍵をどのように生成し、どのような状況で使用できるかを指定できます。Android 6.0 (API レベル 23) ではアプリケーションで正しい鍵の使用を保証するために使用できる KeyGenParameterSpec
クラスを導入しました。以下に例を示します。
KeyGenParameterSpec
は鍵を暗号化および復号化に使用できることを示しますが、署名や検証などの他の目的には使用できません。さらに、ブロックモード (CBC) 、パディング (PKCS #7) を指定し、ランダム化された暗号化が必要である (これがデフォルトです) ことを明示的に指定します。次に、KeyGenerator.getInstance
呼び出しでプロバイダの名前として AndroidKeyStore
を入力し、鍵が Android KeyStore に保存されることを確保します。
GCM は 認証付き暗号 を提供する AES モードであり、HMAC などの別のメカニズムを必要とする CBC などの古いモードとは異なり、暗号化とデータ認証を単一プロセスに統合することでセキュリティを強化します。さらに、GCM はパディングを必要としないため、実装を簡易化し、脆弱性を最小限に抑えます。
上記の仕様に違反して生成された鍵の使用を試みるとセキュリティ例外が発生します。
その鍵を使用して暗号化する例を以下に示します。
IV (初期化ベクトル) および暗号化されたバイト列の両方を保存する必要があります。そうしないと復号はできません。
暗号文を復号する方法を以下に示します。 input
は暗号化されたバイト配列であり、 iv
は暗号ステップからの初期化ベクトルです。
IV は毎回ランダムに生成されるため、後で復号するために暗号文 (encryptedBytes
) とともに保存する必要があります。
Android 6.0 (API レベル 23) より前では AES 鍵の生成はサポートされていませんでした。結果として、多くの実装では RSA を使用することを選択し、 KeyPairGeneratorSpec
を使用して非対称暗号化用の公開鍵と秘密鍵のペアを生成するか、あるいは SecureRandom
を使用して AES 鍵を生成していました。
RSA 鍵ペアの作成に使用される KeyPairGenerator
および KeyPairGeneratorSpec
の例を以下の示します。
この例では 4096 ビットの鍵サイズ (すなわち、モジュラスサイズ) で RSA 鍵ペアを作成します。楕円曲線 (Elliptic Curve, EC) 鍵も同様の方法で生成できます。ただし、Android 11 (API レベル 30) 以降、AndroidKeyStore は EC 鍵での暗号化や復号化をサポートしていません 。これらは署名にのみ使用できます。
対称暗号鍵は Password Based Key Derivation Function version 2 (PBKDF2) を使用してパスフレーズから生成できます。この暗号プロトコルは暗号鍵を生成するように設計されており、暗号化の目的で使用できます。アルゴリズムの入力パラメータは 脆弱な鍵生成関数 セクションに従って調整します。以下のコードはパスワードに基づいて強力な暗号鍵を生成する方法を示しています。
上記の手法ではパスワードと必要なビット長の鍵 (例えば 128 または 256 ビットの AES 鍵) を含む文字配列が必要です。 PBKDF2 アルゴリズムにより使用される 10,000 ラウンドの反復回数を定義します。反復回数を増やすことでパスワードに対するブルートフォース攻撃の作業負荷が大幅に増加しますが、鍵導出にはより多くの計算能力が必要になるためパフォーマンスに影響を与える可能性があります。ビットからバイトに変換するために鍵長を 8 で除算した値に等しいソルトサイズを定義し、 SecureRandom
クラスを使用してランダムにソルトを生成します。同じパスワードが与えられた際には何度でも同じ暗号鍵が生成されることを確実にするために、このソルトは一定に保つ必要があります。ソルトを SharedPreferences
に非公開で格納できることに注意します。リスクの高いデータの場合には同期を防ぐために Android のバックアップメカニズムからソルトを除外することを推奨します。
ルート化デバイスやパッチ適用 (再パッケージなど) されたアプリケーションをデータの脅威として考慮すると、
AndroidKeystore
に配置された鍵でソルトを暗号化するほうがよいかもしれないことに注意します。 Password-Based Encryption (PBE) 鍵は Android 8.0 (API レベル 26) まで、推奨されるPBKDF2WithHmacSHA1
アルゴリズムを使用して生成されます。より高い API レベルではPBKDF2withHmacSHA256
を使用することがベストです。これはハッシュ値が長くなります。
注: NDK を使用して暗号化操作とハードコードされた鍵を隠す必要があるという誤解が広まっています。しかし、このメカニズムを使用しても効果的ではありません。攻撃者は依然としてツールを使用して、使用されているメカニズムを見つけ、メモリ内の鍵のダンプを作成します。次に、制御フローは例えば radare2 と、 Fridaの助けを借りて抽出された鍵、またはその両方を組み合わせた r2frida (詳細は ネイティブコードの逆アセンブル (Disassembling Native Code), プロセス調査 (Process Exploration) を参照) で解析することができます。 Android 7.0 (API レベル 24) 以降では、プライベート API の使用が許可されておらず、代わりにパブリック API を呼び出す必要があります。これは Android 開発者ブログ で説明されているように隠蔽の有効性にさらに影響を与えます。
乱数生成
暗号にはセキュアな擬似乱数生成 (PRNG) が必要です。 java.util.Random
のような標準の Java クラスは十分なランダム性を提供しないため、実際に攻撃者が生成される次の値を推測し、この推測を使用して別のユーザーになりすましたり機密情報にアクセスしたりするおそれがあります。
一般的に、 SecureRandom
を使用すべきです。しかし、Android 4.4 (API レベル 19) 以前の Android バージョンをサポートする場合には、 PRNG を適切に初期化できない Android 4.1-4.3 (API レベル 16-18) バージョンのバグを回避するために更なる注意が必要です。
ほとんどの開発者は引数なしでデフォルトコンストラクタを介して SecureRandom
をインスタンス化する必要があります。他のコンストラクタはより高度な用途のためにあり、誤って使用されると、ランダム性やセキュリティが低下するおそれがあります。 SecureRandom
を支援する PRNG プロバイダは AndroidOpenSSL
(Conscrypt) プロバイダから SHA1PRNG
を使用します。
Last updated