📗
owasp-mastg-ja
  • OWASP Mobile Application Security Testing Guide ja
  • OWASP モバイルアプリケーションセキュリティテストガイド 日本語版
    • 序文
    • 扉
    • OWASP MASVS と MASTG の採用
    • 謝辞
  • 概要
    • OWASP モバイルアプリケーションセキュリティプロジェクトの序文
    • モバイルアプリケーションの分類
    • モバイルアプリケーションのセキュリティテスト
  • 総合テストガイド
    • モバイルアプリの認証アーキテクチャ
    • ネットワーク通信のテスト
    • モバイルアプリの暗号化
    • コード品質のテスト
    • 改竄とリバースエンジニアリング
    • ユーザープライバシー保護のテスト
  • Android テストガイド
    • プラットフォーム概要
    • Android セキュリティテスト入門
    • Android のデータストレージ
    • Android の暗号化 API
    • Android のローカル認証
    • Android のネットワーク通信
    • Android のプラットフォーム API
    • Android アプリのコード品質とビルド設定
    • Android のアンチリバース防御
  • iOS テストガイド
    • プラットフォーム概要
    • iOS セキュリティテスト入門
    • iOS のデータストレージ
    • iOS の暗号化 API
    • iOS のローカル認証
    • iOS のネットワーク通信
    • iOS のプラットフォーム API
    • iOS アプリのコード品質とビルド設定
    • iOS のアンチリバース防御
  • 付録
    • テストツール
    • リファレンスアプリケーション
    • さらに学ぶための資料
Powered by GitBook
On this page
  • 概要
  • 一般的な改善方法
  • セキュリティプロバイダ
  • 鍵生成
  • 乱数生成

Was this helpful?

  1. Android テストガイド

Android の暗号化 API

PreviousAndroid のデータストレージNextAndroid のローカル認証

Last updated 4 months ago

Was this helpful?

概要

の章では、一般的な暗号のベストプラクティスを紹介し、暗号が間違って使用される場合に起こりうる典型的な問題について説明しました。この章では、Android の暗号化 API について詳しく説明します。ソースコード内でのこれらの API の使用を特定する方法とその暗号設定を判断する方法を示します。コードをレビューする際には、使用されている暗号パラメータをこのガイドにリンクされている現行のベストプラクティスと比較するようにしてください。

Android 上の暗号化システムの主要コンポーネントを特定できます。

  • KeyStore - "データストレージのテスト" の章の セクションを参照

  • KeyChain - "データストレージのテスト" の章の セクションを参照

Android 暗号化 API は Java Cryptography Architecture (JCA) をベースとしています。JCA はインタフェースと実装を分離し、暗号化アルゴリズムのセットを実装できる複数の を含めることを可能にしています。 JCA インタフェースのほとんどは java.security.* および javax.crypto.* パッケージで定義されています。さらに、 Android 固有のパッケージ android.security.* および android.security.keystore.* があります。

KeyStore および KeyChain は鍵を保存および使用するための API を提供しています (裏では、 KeyChain API は KeyStore システムを使用しています) 。これらのシステムは暗号鍵のライフサイクル全体を管理することを可能にします。暗号鍵管理を実装するための要件およびガイダンスは に記載されています。以下のフェーズが考えられます。

  • 鍵の生成

  • 鍵の使用

  • 鍵の保管

  • 鍵のアーカイブ

  • 鍵の削除

鍵の保管については の章で解析していますのでご注意ください。

これらのフェーズは KeyStore/KeyChain システムにより管理されます。ただしシステムの動作はアプリケーション開発者の実装方法により異なります。解析プロセスではアプリケーション開発者が使用する機能に焦点を当てる必要があります。以下の機能を特定および検証する必要があります。

  • 鍵ローテーション

最新の API レベルをターゲットとするアプリでは、以下の変更が行われました。

    • Crypto プロバイダのサポートは中止されており、このプロバイダは非推奨です。同じことがセキュアランダムのための SHA1PRNG にも当てはまります。

    • 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 を取得します。

    • getInstance メソッドを使用してセキュリティプロバイダを指定し、 28 未満の API をターゲットにすると、警告が発生します。 Android 9 (API レベル 28) 以上をターゲットにした場合、エラーが発生します。

    • Crypto プロバイダは現在削除されています。これをコールすると NoSuchProviderException が返されます。

一般的な改善方法

アプリ審査の際には以下の推奨事項リストを考慮する必要があります。

  • セキュリティプロバイダの指定を停止し、デフォルト実装 (AndroidOpenSSL, Conscrypt) を使用します。

  • Crypto セキュリティプロバイダとその SHA1PRNG は非推奨であるため使用を停止します。

  • Android KeyStore システムに対してのみセキュリティプロバイダを指定します。

  • IV なしでのパスワードベースの暗号化方式の使用を停止します。

  • KeyPairGeneratorSpec の代わりに KeyGenParameterSpec を使用します。

セキュリティプロバイダ

Android は Java Security サービスの実装を java.security.Provider クラスに依存しています。これらのプロバイダはセキュアなネットワーク通信と、暗号に依存するその他のセキュアな機能を確保するために重要です。

Android に含まれるセキュリティプロバイダのリストは Android のバージョンや OEM 固有のビルドにより異なります。古いバージョンのセキュリティプロバイダの実装の中には安全性が低いものや脆弱性があるものが知られています。したがって、 Android アプリケーションは正しいアルゴリズムを選択して適切な構成を提供するだけでなく、場合によってはレガシーセキュリティプロバイダの実装の強度にも注意を払う必要があります。

以下のコードを使用して既存のセキュリティプロバイダのセットを一覧表示できます。

StringBuilder builder = new StringBuilder();
for (Provider provider : Security.getProviders()) {
    builder.append("provider: ")
            .append(provider.getName())
            .append(" ")
            .append(provider.getVersion())
            .append("(")
            .append(provider.getInfo())
            .append(")\n");
}
String providers = builder.toString();
//now display the string on the screen or in the logs for debugging.

これは Google Play API を備えたエミュレータで実行中の Android 9 (API レベル 28) の出力です。

provider: AndroidNSSP 1.0(Android Network Security Policy Provider)
provider: AndroidOpenSSL 1.0(Android's OpenSSL-backed security provider)
provider: CertPathProvider 1.0(Provider of CertPathBuilder and CertPathVerifier)
provider: AndroidKeyStoreBCWorkaround 1.0(Android KeyStore security provider to work around Bouncy Castle)
provider: BC 1.57(BouncyCastle Security Provider v1.57)
provider: HarmonyJSSE 1.0(Harmony JSSE Provider)
provider: AndroidKeyStore 1.0(Android KeyStore security provider)

セキュリティプロバイダの更新

旧バージョンの Android

dependencies {
  implementation 'org.conscrypt:conscrypt-android:last_version'
}

次に、以下を呼び出してプロバイダを登録する必要があります。

Security.addProvider(Conscrypt.newProvider())

鍵生成

Android SDK は鍵をどのように生成し、どのような状況で使用できるかを指定できます。Android 6.0 (API レベル 23) ではアプリケーションで正しい鍵の使用を保証するために使用できる KeyGenParameterSpec クラスを導入しました。以下に例を示します。

String keyAlias = "MySecretKey";

KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(keyAlias,
        KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
        .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
        .setRandomizedEncryptionRequired(true)
        .build();

KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES,
        "AndroidKeyStore");
keyGenerator.init(keyGenParameterSpec);

SecretKey secretKey = keyGenerator.generateKey();

KeyGenParameterSpec は鍵を暗号化および復号化に使用できることを示しますが、署名や検証などの他の目的には使用できません。さらに、ブロックモード (CBC) 、パディング (PKCS #7) を指定し、ランダム化された暗号化が必要である (これがデフォルトです) ことを明示的に指定します。次に、KeyGenerator.getInstance 呼び出しでプロバイダの名前として AndroidKeyStore を入力し、鍵が Android KeyStore に保存されることを確保します。

上記の仕様に違反して生成された鍵の使用を試みるとセキュリティ例外が発生します。

その鍵を使用して暗号化する例を以下に示します。

String AES_MODE = KeyProperties.KEY_ALGORITHM_AES
        + "/" + KeyProperties.BLOCK_MODE_CBC
        + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7;
KeyStore AndroidKeyStore = AndroidKeyStore.getInstance("AndroidKeyStore");

// byte[] input
Key key = AndroidKeyStore.getKey(keyAlias, null);

Cipher cipher = Cipher.getInstance(AES_MODE);
cipher.init(Cipher.ENCRYPT_MODE, key);

byte[] encryptedBytes = cipher.doFinal(input);
byte[] iv = cipher.getIV();
// save both the IV and the encryptedBytes

IV (初期化ベクトル) および暗号化されたバイト列の両方を保存する必要があります。そうしないと復号はできません。

暗号文を復号する方法を以下に示します。 input は暗号化されたバイト配列であり、 iv は暗号ステップからの初期化ベクトルです。

// byte[] input
// byte[] iv
Key key = AndroidKeyStore.getKey(AES_KEY_ALIAS, null);

Cipher cipher = Cipher.getInstance(AES_MODE);
IvParameterSpec params = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, key, params);

byte[] result = cipher.doFinal(input);

IV は毎回ランダムに生成されるため、後で復号するために暗号文 (encryptedBytes) とともに保存する必要があります。

Android 6.0 (API レベル 23) より前では AES 鍵の生成はサポートされていませんでした。結果として、多くの実装では RSA を使用することを選択し、 KeyPairGeneratorSpec を使用して非対称暗号化用の公開鍵と秘密鍵のペアを生成するか、あるいは SecureRandom を使用して AES 鍵を生成していました。

RSA 鍵ペアの作成に使用される KeyPairGenerator および KeyPairGeneratorSpec の例を以下の示します。

Date startDate = Calendar.getInstance().getTime();
Calendar endCalendar = Calendar.getInstance();
endCalendar.add(Calendar.YEAR, 1);
Date endDate = endCalendar.getTime();
KeyPairGeneratorSpec keyPairGeneratorSpec = new KeyPairGeneratorSpec.Builder(context)
        .setAlias(RSA_KEY_ALIAS)
        .setKeySize(4096)
        .setSubject(new X500Principal("CN=" + RSA_KEY_ALIAS))
        .setSerialNumber(BigInteger.ONE)
        .setStartDate(startDate)
        .setEndDate(endDate)
        .build();

KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA",
        "AndroidKeyStore");
keyPairGenerator.initialize(keyPairGeneratorSpec);

KeyPair keyPair = keyPairGenerator.generateKeyPair();
public static SecretKey generateStrongAESKey(char[] password, int keyLength)
{
    //Initialize objects and variables for later use
    int iterationCount = 10000;
    int saltLength     = keyLength / 8;
    SecureRandom random = new SecureRandom();
    //Generate the salt
    byte[] salt = new byte[saltLength];
    random.nextBytes(salt);
    KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iterationCount, keyLength);
    SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
    byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
    return new SecretKeySpec(keyBytes, "AES");
}

上記の手法ではパスワードと必要なビット長の鍵 (例えば 128 または 256 ビットの AES 鍵) を含む文字配列が必要です。 PBKDF2 アルゴリズムにより使用される 10,000 ラウンドの反復回数を定義します。反復回数を増やすことでパスワードに対するブルートフォース攻撃の作業負荷が大幅に増加しますが、鍵導出にはより多くの計算能力が必要になるためパフォーマンスに影響を与える可能性があります。ビットからバイトに変換するために鍵長を 8 で除算した値に等しいソルトサイズを定義し、 SecureRandom クラスを使用してランダムにソルトを生成します。同じパスワードが与えられた際には何度でも同じ暗号鍵が生成されることを確実にするために、このソルトは一定に保つ必要があります。ソルトを SharedPreferences に非公開で格納できることに注意します。リスクの高いデータの場合には同期を防ぐために Android のバックアップメカニズムからソルトを除外することを推奨します。

ルート化デバイスやパッチ適用 (再パッケージなど) されたアプリケーションをデータの脅威として考慮すると、 AndroidKeystore に配置された鍵でソルトを暗号化するほうがよいかもしれないことに注意します。 Password-Based Encryption (PBE) 鍵は Android 8.0 (API レベル 26) まで、推奨される PBKDF2WithHmacSHA1 アルゴリズムを使用して生成されます。より高い API レベルでは PBKDF2withHmacSHA256 を使用することがベストです。これはハッシュ値が長くなります。

乱数生成

暗号にはセキュアな擬似乱数生成 (PRNG) が必要です。 java.util.Random のような標準の Java クラスは十分なランダム性を提供しないため、実際に攻撃者が生成される次の値を推測し、この推測を使用して別のユーザーになりすましたり機密情報にアクセスしたりするおそれがあります。

ほとんどの開発者は引数なしでデフォルトコンストラクタを介して SecureRandom をインスタンス化する必要があります。他のコンストラクタはより高度な用途のためにあり、誤って使用されると、ランダム性やセキュリティが低下するおそれがあります。 SecureRandom を支援する PRNG プロバイダは AndroidOpenSSL (Conscrypt) プロバイダから SHA1PRNG を使用します。

Android 7.0 (API level 24) 以上について 。

セキュリティプロバイダの指定を停止することを推奨します。代わりに、常にパッチされた を使用します。

Android 8.1 (API レベル 27) 以上について は以下のように記しています。

Android 9 (API レベル 28) 以上について はさらに多くの変更を記しています。

Android 10 (API レベル 29) について にすべてのネットワークセキュリティの変更がリストされています。

の章で説明されているベストプラクティスが守られていることを確認します。

セキュリティプロバイダが最新アップデートであることを確認します - 。

コンポーネントに最新のパッチを適用し続けることはセキュリティ原則の一つです。同じことが provider にも当てはまります。アプリケーションは使用されているセキュリティプロバイダが最新かどうかを確認し、最新でない場合には 。

古いバージョンの Android (例: Android 7.0 (API レベル 24) より以前のバージョンのみ使用) をサポートする一部のアプリケーションでは、最新のライブラリをバンドルすることが唯一の選択肢かもしれません。Conscrypt ライブラリはさまざまな API レベルで暗号化の一貫性を保ち、より重いライブラリである をインポートする必要がないようにするため、この状況では適切な選択といえます。

は以下の方法でインポートできます。

GCM は を提供する AES モードであり、HMAC などの別のメカニズムを必要とする CBC などの古いモードとは異なり、暗号化とデータ認証を単一プロセスに統合することでセキュリティを強化します。さらに、GCM はパディングを必要としないため、実装を簡易化し、脆弱性を最小限に抑えます。

この例では 4096 ビットの鍵サイズ (すなわち、モジュラスサイズ) で RSA 鍵ペアを作成します。楕円曲線 (Elliptic Curve, EC) 鍵も同様の方法で生成できます。ただし、Android 11 (API レベル 30) 以降、 。これらは署名にのみ使用できます。

対称暗号鍵は Password Based Key Derivation Function version 2 (PBKDF2) を使用してパスフレーズから生成できます。この暗号プロトコルは暗号鍵を生成するように設計されており、暗号化の目的で使用できます。アルゴリズムの入力パラメータは セクションに従って調整します。以下のコードはパスワードに基づいて強力な暗号鍵を生成する方法を示しています。

注: NDK を使用して暗号化操作とハードコードされた鍵を隠す必要があるという誤解が広まっています。しかし、このメカニズムを使用しても効果的ではありません。攻撃者は依然としてツールを使用して、使用されているメカニズムを見つけ、メモリ内の鍵のダンプを作成します。次に、制御フローは例えば radare2 と、 Fridaの助けを借りて抽出された鍵、またはその両方を組み合わせた (詳細は , を参照) で解析することができます。 Android 7.0 (API レベル 24) 以降では、プライベート API の使用が許可されておらず、代わりにパブリック API を呼び出す必要があります。これは で説明されているように隠蔽の有効性にさらに影響を与えます。

一般的に、 SecureRandom を使用すべきです。しかし、Android 4.4 (API レベル 19) 以前の Android バージョンをサポートする場合には、 Android 4.1-4.3 (API レベル 16-18) バージョンのバグを回避するために更なる注意が必要です。

詳細については を確認してください。

Android 開発者ブログでは以下のように記しています
開発者ドキュメント
Android 開発者ブログ
開発者ドキュメント
"モバイルアプリの暗号化"
セキュリティプロバイダの更新
更新してください
Bouncy Castle
Conscrypt for Android
認証付き暗号
AndroidKeyStore は EC 鍵での暗号化や復号化をサポートしていません
r2frida
ネイティブコードの逆アセンブル (Disassembling Native Code)
プロセス調査 (Process Exploration)
Android 開発者ブログ
PRNG を適切に初期化できない
Android ドキュメント
セキュリティプロバイダ
"モバイルアプリの暗号化"
セキュリティプロバイダ
Key Management Cheat Sheet
"データストレージのテスト"
セキュリティプロバイダ
鍵生成
乱数値生成
脆弱な鍵生成関数
KeyStore
KeyChain