メタデータ暗号化

Android 7.0 以降では、ファイルベース暗号化(FBE)がサポートされています。FBE を使用すると、さまざまなファイルをそれぞれ異なる鍵で暗号化して、個別にロック解除することができます。これらの鍵は、ファイルの内容と名前の両方を暗号化するために使用されます。FBE を使用した場合、ディレクトリ レイアウトやファイルサイズ、パーミッション、作成日時 / 変更日時など、他の情報は暗号化されません。このような他の情報は、総称して「ファイル システム メタデータ」と呼ばれます。

Android 9 で、メタデータ暗号化がサポートされるようになりました。メタデータ暗号化は、FBE によって暗号化されないコンテンツを、ブート時に提供される 1 つの鍵で暗号化します。この鍵は Keymaster によって保護され、Keymaster はセキュアブートによって保護されます。

Adoptable Storage の場合、FBE を有効にすると、必ずメタデータ暗号化も有効になります。また、メタデータ暗号化は、内部ストレージに対して有効にすることもできます。

内部ストレージに対する実装

新しいデバイスの内部ストレージに対してメタデータ暗号化をセットアップするには、metadata ファイル システムをセットアップして、init シーケンスを変更し、デバイスの fstab ファイル内でメタデータ暗号化を有効にします。

前提条件

メタデータ暗号化をセットアップできるのは、データ パーティションを最初にフォーマットするときに限られます。そのため、この機能は新しいデバイス専用であり、OTA で変更するものではありません。

メタデータ暗号化を使用するには、カーネル内で dm-default-key モジュールを有効にする必要があります。Android R 以降の場合、dm-default-key は、Android 共通カーネル(バージョン 4.14 以降)によってサポートされています。このバージョンの dm-default-key は、ハードウェアとベンダーに依存しない暗号化フレームワーク「blk-crypto」を使用します。

dm-default-key を有効にするには、以下を使用します。

    CONFIG_BLK_INLINE_ENCRYPTION=y
    CONFIG_FS_ENCRYPTION_INLINE_CRYPT=y
    CONFIG_DM_DEFAULT_KEY=y
    

dm-default-key は、利用可能な場合、インライン暗号化ハードウェア(ストレージ デバイスと送受信している最中にデータの暗号化 / 復号を行うハードウェア)を使用します。インライン暗号化ハードウェアを使用しない場合は、カーネルの暗号化 API に対するフォールバックも有効にする必要があります。

    CONFIG_BLK_INLINE_ENCRYPTION_FALLBACK=y
    

インライン暗号化ハードウェアを使用しない場合は、FBE で推奨されているとおり、CPU ベース アクセラレーションも有効にしてください。

Android 10 以前の場合、dm-default-key は、Android 共通カーネルによってサポートされていませんでした。そのため、dm-default-key の実装はベンダーに任されていました。

メタデータ ファイル システムをセットアップする

メタデータ暗号鍵がないとユーザーデータ パーティション内のデータを読み取れないため、この鍵を保護する keymaster blob を格納できるように、パーティション テーブルに「メタデータ パーティション」という独立したパーティションを確保する必要があります。メタデータ パーティションには 16 MB 必要です。

fstab.hardware には、起動時にフォーマットされていることを保証する formattable フラグを含む、/metadata でマウントされるパーティションに存在するメタデータ ファイルシステムのエントリを含める必要があります。f2fs ファイルシステムは小さいパーティションでは動作しないため、代わりに ext4 を使用することをおすすめします。次に例を示します。

/dev/block/bootdevice/by-name/metadata              /metadata          ext4        noatime,nosuid,nodev,discard                          wait,check,formattable

/metadata マウント ポイントが確実に存在するように、次の行を BoardConfig-common.mk に追加します。

BOARD_USES_METADATA_PARTITION := true

init シーケンスの変更

メタデータ暗号化を使用する場合、/data がマウントされるためには、まず vold が実行されている必要があります。早期に開始されるように、次のスタンザを init.hardware.rc に追加します。

# We need vold early for metadata encryption
    on early-fs
        start vold

init が /data のマウントを試行するには、Keymaster が動作して準備が整っている必要があります。

init.hardware.rcon late-fs スタンザには、/data 自体をマウントする mount_all 命令がすでに含まれています。この行の前に、wait_for_keymaster サービスを実行するディレクティブを追加します。

on late-fs
       …
        # Wait for keymaster
        exec_start wait_for_keymaster

        # Mount RW partitions which need run fsck
        mount_all /vendor/etc/fstab.${ro.boot.hardware.platform} --late

メタデータ暗号化を有効にする

最後に、userdatafstab エントリの fs_mgr_flags 列に keydirectory=/metadata/vold/metadata_encryption を追加します。たとえば、完全な fstab 行は次のようになります。

/dev/block/bootdevice/by-name/userdata              /data              f2fs        noatime,nosuid,nodev,discard,inlinecrypt latemount,wait,check,fileencryption=aes-256-xts:aes-256-cts:inlinecrypt_optimized,keydirectory=/metadata/vold/metadata_encryption,quota,formattable

内部ストレージのメタデータ暗号化アルゴリズムは、デフォルトでは AES-256-XTS です。この設定は、同様に fs_mgr_flags 列で metadata_encryption オプションを設定することでオーバーライドできます。

  • AES アクセラレーションを搭載していないデバイスの場合、metadata_encryption=adiantum を設定することで、Adiantum 暗号化を有効にすることができます。
  • インライン暗号化ハードウェアに直接プロビジョニングされるハードウェア ラップ鍵をサポートしているデバイスの場合、metadata_encryption=aes-256-xts:wrappedkey_v0 を設定することで、メタデータ暗号鍵をハードウェア ラップできます。ハードウェア ラップ鍵とその前提条件については、FBE をご覧ください。

検証

Android R 以降の場合、以下のコマンドを実行して、内部ストレージに対してメタデータ暗号化が有効になっているか、そして意図どおりの設定が使用されているか検証します。

    adb root
    adb shell dmctl table userdata
    

出力は次のようになります。

    Targets in the device-mapper table for userdata:
    0-4194304: default-key, aes-xts-plain64 - 0 252:2 0 3 allow_discards sector_size:4096 iv_large_sectors
    

Adiantum 暗号化を有効にしている場合、3 番目のフィールドは xchacha12,aes-adiantum-plain64 になります。

また、以下のよくある問題に注意しながら、下記で説明している追加テストを実行してください。

よくある問題

メタデータ暗号化済みの /data パーティションをマウントする mount_all の呼び出し中に、init が vdc ツールを実行します。vdc ツールは、binder を介して vold に接続し、メタデータ暗号化済みのデバイスを設定してパーティションをマウントします。この呼び出しの間は init がブロックされ、init プロパティの読み取りまたは設定の試行は、mount_all が終了するまでブロックされます。この段階で、プロパティの読み取りまたは設定に関する vold のいずれかの作業が直接的または間接的にブロックされると、デッドロックが発生します。voldinit の介入なしで、鍵の読み取り、Keymaster とのやり取り、データ ディレクトリのマウントを完了できることが重要です。

Keymaster は mount_all の実行時に完全に起動していないと、init から特定のプロパティを読み込むまで vold に応答しません。その結果、上述のデッドロックが発生します。前述したように、関連する mount_all 呼び出しの上に exec_start wait_for_keymaster を追加すると、事前に Keymaster が完全に動作して、このデッドロックを回避できます。

メタデータ暗号化のテスト

これらのテストは間もなく完成しますが、それまでの間は Android.bp に数行を追加し、platform/system/voldcheck_encryption.cpp を追加して実装をテストしてください。

Android.bp の変更

以下に示すように Android.bp を変更します。

...
    }

    cc_binary {
        name: "vold",
        defaults: [
            "vold_default_flags",
            "vold_default_libs",
        ],

        srcs: ["main.cpp"],
        static_libs: ["libvold"],
        product_variables: {
            arc: {
                static_libs: [
                    "arc_services_aidl",
                    "libarcobbvolume",
                ],
            },
        },
        init_rc: [
            "vold.rc",
            "wait_for_keymaster.rc",
        ],

        required: [
            "check_encryption",
            "mke2fs",
            "vold_prepare_subdirs",
            "wait_for_keymaster",
        ],
    }
    ...
    
    }

    cc_binary {
        name: "check_encryption",
        defaults: ["vold_default_flags"],
        srcs: [
            "FileDeviceUtils.cpp",
            "check_encryption.cpp",
        ],
        shared_libs: [
            "libbase",
        ],
    }

check_encryption.cpp の追加

check_encryption.cppplatform/system/vold に追加します。

/*
     * Copyright (C) 2017 The Android Open Source Project
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    #include "FileDeviceUtils.h"

    #include <cmath>
    #include <string>

    #include <assert.h>
    #include <stdio.h>

    #include <android-base/file.h>
    #include <android-base/logging.h>
    #include <android-base/unique_fd.h>
    using android::base::unique_fd;
    using android::base::ReadFileToString;
    using android::base::WriteStringToFile;

    namespace android {
    namespace vold {
    const size_t sectorsize = 1 << 12;
    const int sectorcount = 1024;
    static double randomness_score(const std::string& checkme) {
        unsigned int freq[256] = {0};
        unsigned int sum = 256;
        double loglaplace = 0;
        for (auto b : checkme) {
            loglaplace -= 8 + log2(static_cast<double>(++freq[static_cast<uint8_t>(b)]) / (sum++));
        }
        return loglaplace;
        LOG(INFO) << "Score: " << loglaplace;  // if negative, not random
        return loglaplace < 0;
    }
    static bool run_test(const std::string& device) {
        unique_fd device_fd(open(device.c_str(), O_RDONLY | O_CLOEXEC));
        if (device_fd.get() == -1) {
            PLOG(ERROR) << "Failed to open " << device;
            return false;
        }
        int randompassed = 0;
        auto buf = std::string(sectorsize, '\0');
        for (int i = 0; i < sectorcount; i++) {
            auto l = read(device_fd.get(), &buf[0], buf.size());
            if (l < 1) {
                PLOG(ERROR) << "Failed read on sector " << i;
                return false;
            }
            if (((size_t)l) != buf.size()) {
                LOG(ERROR) << "Short read on sector " << i;
                return false;
            }
            auto score = randomness_score(buf);
            if (score >= 0) {
                randompassed++;
                LOG(INFO) << "Passed randomness check on sector " << i << " with score " << score;
            } else {
                LOG(ERROR) << "Failed randomness check on sector " << i << " with score " << score;
            }
        }
        LOG(INFO) << "Passed randomness check on " << randompassed << "/" << sectorcount << " sectors";
        return randompassed == sectorcount;
    }
    }  // namespace vold
    }  // namespace android
    int main(int argc, const char* const argv[]) {
        setenv("ANDROID_LOG_TAGS", "*:v", 1);
        android::base::InitLogging(const_cast<char**>(argv), android::base::StderrLogger);
        if (argc != 2) {
            LOG(ERROR) << "Usage: " << argv[0] << " <device>";
            LOG(ERROR) << "example: " << argv[0] << " /dev/block/bootdevice/by-name/userdata";
            return -1;
        }
        android::vold::run_test(std::string(argv[1]));
        return 0;
    }

Adoptable Storage 上の構成

Android 9 以降の場合、内部ストレージに対してメタデータ暗号化が有効になっていなくても、FBE が有効になっている場合は常に Adoptable Storage に対してメタデータ暗号化が有効になります。

現在の方法

Android R 以降を搭載しているデバイスの場合、Adoptable Storage のメタデータ暗号化は、内部ストレージと同様、dm-default-key カーネル モジュールを使用します。有効にするカーネル構成オプションについては、上記の前提条件をご覧ください。デバイスの内部ストレージ上で動作するインライン暗号化ハードウェアが Adoptable Storage 上では利用できない場合があり、そのため CONFIG_BLK_INLINE_ENCRYPTION_FALLBACK=y が必要になることがあります。

デフォルトでは、dm-default-key ボリューム メタデータ暗号化方式は、AES-256-XTS 暗号化アルゴリズム(4096 バイトの暗号化セクター)を使用します。このアルゴリズムは、ro.crypto.volume.metadata.encryption システム プロパティを設定することでオーバーライドできます。このプロパティの値の構文は、上記の metadata_encryption fstab オプションと同じです。たとえば、AES アクセラレーションを搭載していないデバイスの場合、ro.crypto.volume.metadata.encryption=adiantum を設定することで、Adiantum 暗号化を有効にすることができます。

以前の方法

Android 10 以前を搭載しているデバイスの場合、Adoptable Storage のメタデータ暗号化は、dm-default-key ではなく、dm-crypt カーネル モジュールを使用します。

    CONFIG_DM_CRYPT=y
    

dm-default-key メソッドと異なり、dm-crypt メソッドの場合、ファイル コンテンツが 2 回暗号化されます(1 回目は FBE 鍵で、2 回目はメタデータ暗号鍵で暗号化されます)。この二重暗号化は、パフォーマンスを低下させるものである一方、メタデータ暗号化のセキュリティ目標を達成するうえで必須ではありません(FBE 鍵は少なくともメタデータ暗号鍵と同じようにサイバー攻撃に強いことが Android によって保証されています)。ベンダーは、カーネルをカスタマイズして、二重暗号化を回避することができます。特に、allow_encrypt_override オプションを実装する方法があります。このオプションは、ro.crypto.allow_encrypt_override システム プロパティが true に設定されている場合に、dm-crypt に渡されます。このようなカスタマイズは、Android 共通カーネルによってサポートされていません。

dm-crypt ボリューム メタデータ暗号化方式は、デフォルトでは AES-128-CBC 暗号化アルゴリズム(ESSIV、512 バイトの暗号化セクター)を使用します。この設定は、以下のシステム プロパティを設定することでオーバーライドできます(FDE に対しても使用されます)。

  • ro.crypto.fde_algorithm: メタデータ暗号化アルゴリズムを選択します。選択肢は、aes-128-cbcadiantum です。Adiantum を使用できるのは、デバイスに AES アクセラレーションが搭載されていない場合に限られます。
  • ro.crypto.fde_sector_size: 暗号セクターのサイズを選択します。選択肢は、512、1024、2048、4096 です。Adiantum 暗号化の場合は、4096 を使用します。