多くのディスクおよびファイル暗号化ソフトウェアと同様に、Android のストレージ暗号化では、以前からシステムメモリに存在する未加工の暗号鍵に依存して暗号化を実行しています。暗号化がソフトウェアではなく専用ハードウェアによって行われる場合でも、ソフトウェアは通常、未加工の暗号鍵を管理する必要があります。
これらの鍵はオフライン攻撃(ストレージの暗号化が防御対象とする主な攻撃タイプ)で使われることはないため、これまでこの状態は問題とはみなされてきませんでした。しかし、コールドブート攻撃や、攻撃者がデバイスを完全に侵害することなくシステムメモリをリークできる可能性があるオンライン攻撃など、他のタイプの攻撃に対する保護を強化したいというニーズも存在します。
この問題を解決するため、Android 11 では、ハードウェアのサポートがあるハードウェアでラップされた鍵のサポートが導入されました。 ハードウェアでラップされた鍵は、専用ハードウェアのみが未加工の形式で内容を知ることのできるストレージ鍵です。ソフトウェアは、これらの鍵をラップされた(暗号化された)形式でのみ、読み取ったり操作したりできます。専用ハードウェアは、ストレージ鍵の生成とインポート、ストレージ鍵の一時的および長期的な形式でのラッピング、サブ鍵の導出ができるだけでなく、サブ鍵をインライン暗号化エンジンに直接プログラミングし、ソフトウェアに別のサブ鍵を返すことができる必要があります。
注: インライン暗号化エンジン(インライン暗号化ハードウェア)とは、データがストレージ デバイス間を移動する際に、データの暗号化と復号を行うハードウェアを指します。これは通常、対応する JEDEC 仕様で定義された暗号化拡張機能を実装する UFS または eMMC ホスト コントローラです。
設計
このセクションでは、必要なハードウェア サポートなど、ハードウェアでラップされた鍵機能の設計について説明します。ここではファイルベースの暗号化(FBE)に焦点を当てていますが、このソリューションはメタデータ暗号化にも適用されます。
システムメモリで未加工の暗号鍵を必要としないようにする方法の 1 つは、暗号鍵をインライン暗号化エンジンの鍵スロットでのみ保持することです。ただし、このアプローチでは次のような問題が発生します。
- 暗号鍵の数が鍵スロットの数より多い場合があります。
- インライン暗号化エンジンは、ディスク上のデータブロック全体の暗号化と復号にのみ使用できます。ただし、FBE の場合、ソフトウェアは引き続き、ファイル名の暗号化や鍵識別子の導出など、他の暗号関連の処理を行える必要があります。ソフトウェアがこれらの処理を行うには、これまでと同様に未加工の FBE 鍵にアクセスできる必要があります。
こうした問題を回避するために、ストレージ鍵はハードウェアでラップされた鍵に変更されています。ハードウェアでラップされた鍵は、専用のハードウェアのみがラップ解除して使用できます。これにより、無制限の数の鍵をサポートできるようになります。さらに、鍵の階層が変更され、一部がこのハードウェアに移動されています。これにより、インライン暗号化エンジンを使用できないタスクのサブ鍵をソフトウェアに返すことができます。
鍵の階層
鍵は、HKDF などの KDF(鍵導出関数)を使用して、他の鍵から導出できます。これにより、鍵の階層が形成されます。
次の図は、ハードウェアでラップされた鍵を使用しない場合の、FBE の一般的な鍵の階層を示しています。
FBE クラス鍵は、Android が Linux カーネルに渡す未加工の暗号鍵です。この鍵は、特定の Android ユーザーの認証情報暗号化ストレージなど、暗号化されたディレクトリ セットのロック解除に使用されます(カーネルでは、この鍵は fscrypt マスター鍵と呼ばれます)。この鍵から、カーネルは次のサブ鍵を導出します。
- 鍵識別子。これは暗号化に使用されるものではなく、特定のファイルまたはディレクトリが保護されている鍵を識別するために使用される値です。
- ファイルの内容の暗号鍵
- ファイル名の暗号鍵
次の図は対照的に、ハードウェアでラップされた鍵を使用する場合の FBE の鍵の階層を示しています。
前のケースとは異なり、鍵の階層にレベルが追加されたほか、ファイルの内容の暗号鍵が再配置されています。ルートノードは引き続き、暗号化されたディレクトリ セットのロックを解除するために Android が Linux に渡す鍵を表しています。ただし、鍵が一時的にラップされた形式になっているため、使用するには専用ハードウェアに渡す必要があります。このハードウェアには、一時的にラップされた鍵を取得するために、次の 2 つのインターフェースを実装する必要があります。
inline_encryption_key
を導出し、これをインライン暗号化エンジンの鍵スロットに直接プログラミングするインターフェース。これにより、未加工の鍵にアクセスできるソフトウェアがなくても、ファイルの内容を暗号化または復号できます。Android 共通カーネルでは、このインターフェースはストレージ ドライバによって実装される必要があるblk_crypto_ll_ops::keyslot_program
オペレーションに対応しています。sw_secret
(「ソフトウェア シークレット」。一部では「未加工のシークレット」とも呼ばれます)を導出して返すインターフェース。ソフトウェア シークレットは、Linux がファイルの内容の暗号化以外のすべてのサブ鍵を導出する際に使用される鍵です。Android 共通カーネルでは、このインターフェースはストレージ ドライバによって実装される必要があるblk_crypto_ll_ops::derive_sw_secret
オペレーションに対応しています。
未加工のストレージ鍵から inline_encryption_key
と sw_secret
を導出するには、ハードウェアで暗号的に強力な KDF を使用する必要があります。この KDF のセキュリティ強度は、暗号のベスト プラクティスに沿って 256 ビット以上(後でどのアルゴリズムでも使える強度)にする必要があります。また、サブ鍵の各タイプを導出する際には、導出されたサブ鍵が暗号的に確実に分離されるように、個別のラベル、コンテキスト、アプリケーション固有の情報文字列を使用する必要があります。これにより、サブ鍵の 1 つが漏洩した場合でも他のサブ鍵を知られることはなくなります。未加工のストレージ鍵はすでに均一にランダムな鍵になっているため、鍵のストレッチングは不要です。
技術的には、セキュリティ要件を満たすものであれば、どの KDF でも使用できます。
ただし、テストの場合はテストコードに同じ KDF を再実装する必要があります。現在、1 つの KDF のレビューと実装が完了し、vts_kernel_encryption_test
のソースコードで公開されています。ハードウェアではこの KDF を使用することをおすすめします。この KDF は、NIST SP 800-108「KDF in Counter Mode」を使用し、PRF として AES-256-CMAC を使用しています。互換性を確保するには、各サブ鍵の KDF コンテキストやラベルの選択など、アルゴリズムのあらゆる部分を同じにする必要があります。
鍵のラッピング
ハードウェアでラップされた鍵のセキュリティ目標を達成するために、2 種類の鍵のラッピングが定義されています。
- 一時的なラッピング: ハードウェアは、起動ごとにランダムに生成される鍵を使用して未加工の鍵を暗号化します。この鍵はハードウェアの外部に直接公開されることはありません。
- 長期的なラッピング: ハードウェアに組み込まれた一意の永続的な鍵を使用して未加工の鍵を暗号化します。この鍵はハードウェアの外部に直接公開されることはありません。
ストレージをロック解除するために Linux カーネルに渡されるすべての鍵は、一時的にラップされます。これにより、攻撃者がシステムメモリから使用中の鍵を抽出できたとしても、その鍵はデバイス外では使用できず、再起動後にはデバイス上でも使用できなくなります。
それと同時に、最初の段階でロックを解除してもらえるように、Android は引き続き鍵の暗号化バージョンをディスクに保存できる必要があります。未加工の鍵をこの目的で使用することもできますが、鍵が起動時に抽出されてデバイス外で使用されることのないように、未加工の鍵がシステムメモリ内に存在しないようにすることをおすすめします。こうした理由から、長期的なラッピングのコンセプトが定義されています。
この 2 種類の方法でラップされた鍵の管理をサポートするには、ハードウェアに次のインターフェースを実装する必要があります。
- ストレージ鍵を生成してインポートし、長期的にラップされた形式で返すインターフェース。これらのインターフェースは KeyMint を介して間接的にアクセスされ、
TAG_STORAGE_KEY
KeyMint タグに対応します。「生成」機能では、Android が使用する新しいストレージ鍵の生成にvold
が使用されます。「インポート」機能では、vts_kernel_encryption_test
がテスト鍵のインポートに使用されます。 - 長期的にラップされたストレージ鍵を一時的にラップされたストレージ鍵に変換するインターフェース。これは、
convertStorageKeyToEphemeral
KeyMint メソッドに対応します。このメソッドは、ストレージのロックを解除するためにvold
とvts_kernel_encryption_test
の両方で使用されます。
鍵のラッピングのアルゴリズムは実装の詳細ですが、ランダムな IV による AES-256-GCM などの強力な AEAD を使用する必要があります。
ソフトウェア側で必要な変更
AOSP には、ハードウェアでラップされた鍵をサポートする基本的なフレームワークがすでに用意されています。これには、vold
などのユーザー空間コンポーネントのサポートと、blk-crypto、fscrypt、dm-default-key における Linux カーネルのサポートが含まれます。
ただし、実装に固有の変更がいくつか必要になります。
KeyMint の変更
TAG_STORAGE_KEY
をサポートし、convertStorageKeyToEphemeral
メソッドを実装するように、デバイスの KeyMint 実装を変更する必要があります。
Keymaster では、convertStorageKeyToEphemeral
の代わりに exportKey
が使用されていました。
Linux カーネルの変更
ハードウェアでラップされた鍵をサポートするように、デバイスのインライン暗号化エンジンの Linux カーネル ドライバを変更する必要があります。
android14
以降のカーネルの場合、blk_crypto_profile::key_types_supported
に BLK_CRYPTO_KEY_TYPE_HW_WRAPPED
を設定し、ハードウェアでラップされた鍵のプログラミングや削除を blk_crypto_ll_ops::keyslot_program
と blk_crypto_ll_ops::keyslot_evict
がサポートするようにし、blk_crypto_ll_ops::derive_sw_secret
を実装します。
android12
カーネルと android13
カーネルの場合、blk_keyslot_manager::features
に BLK_CRYPTO_FEATURE_WRAPPED_KEYS
を設定し、ハードウェアでラップされた鍵のプログラミングや削除を blk_ksm_ll_ops::keyslot_program
と blk_ksm_ll_ops::keyslot_evict
がサポートするようにし、blk_ksm_ll_ops::derive_raw_secret
を実装します。
android11
カーネルの場合、keyslot_manager::features
に BLK_CRYPTO_FEATURE_WRAPPED_KEYS
を設定し、ハードウェアでラップされた鍵のプログラミングや削除を keyslot_mgmt_ll_ops::keyslot_program
と keyslot_mgmt_ll_ops::keyslot_evict
がサポートするようにし、keyslot_mgmt_ll_ops::derive_raw_secret
を実装します。
テスト
ハードウェアでラップされた鍵による暗号化は、標準の鍵による暗号化よりもテストするのが難しくなりますが、テスト鍵をインポートし、ハードウェアが行う鍵の導出を再実装することでテストすることが可能です。これは vts_kernel_encryption_test
で実装されています。このテストを行うには、次のコマンドを実行します。
atest -v vts_kernel_encryption_test
テストログを読み、ハードウェアでラップされた鍵のテストケース(FBEPolicyTest.TestAesInlineCryptOptimizedHwWrappedKeyPolicy
、DmDefaultKeyTest.TestHwWrappedKey
など)が、サポートが検出されないことが原因でスキップされていないか確認します(その場合でもテスト結果は「合格」となります)。
有効化
デバイスのハードウェアでラップされた鍵のサポートが正しく機能するようになったら、デバイスの fstab
ファイルに次の変更を加えて、Android が FBE とメタデータ暗号化にその情報を使用できるようにします。
- FBE:
fileencryption
パラメータにwrappedkey_v0
フラグを追加します。たとえば、fileencryption=::inlinecrypt_optimized+wrappedkey_v0
を使用します。 詳細については、FBE のドキュメントをご覧ください。 - メタデータ暗号化:
metadata_encryption
パラメータにwrappedkey_v0
フラグを追加します。たとえば、metadata_encryption=:wrappedkey_v0
を使用します。 詳細については、メタデータ暗号化のドキュメントをご覧ください。