このドキュメントでは、AES-GCM-HKDF ストリーミング キーで表される数学関数を正式に定義します。この関数は、proto 形式で type.googleapis.com/google.crypto.tink.AesGcmHkdfStreamingKey
としてエンコードされます。
この暗号化は、HRRV151 に基づいています。セキュリティ分析については、HS202 を参照してください。
キーとパラメータ
キーは次の部分で記述されます(このドキュメントのサイズはすべてバイト単位です)。
- \(\mathrm{KeyValue}\): バイト文字列。
- \(\mathrm{CiphertextSegmentSize} \in \{1, 2, \ldots, 2^{31}-1\}\)。
- \(\mathrm{DerivedKeySize} \in \{16, 32\}\)
- \(\mathrm{HkdfHashType} \in \{\mathrm{SHA1}, \mathrm{SHA256}, \mathrm{SHA512}\}\)。
有効なキーは、さらに次のプロパティを満たしています。
- \(\mathrm{len}(\mathrm{KeyValue}) \geq \mathrm{DerivedKeySize}\)。
- \(\mathrm{CiphertextSegmentSize} > \mathrm{DerivedKeySize} + 24\) (後で説明するように、これは \(\mathrm{len}(\mathrm{Header}) + 16\) と同じです)。
これらのプロパティのいずれも満たさない鍵は、鍵が解析されるとき、または対応するプリミティブが作成されるときに、Tink によって拒否されます。
暗号化関数
\(\mathrm{Msg}\) 関連データ\(\mathrm{AssociatedData}\)を含むメッセージを暗号化するには、ヘッダーを作成し、メッセージをセグメントに分割し、各セグメントを暗号化して、暗号化されたセグメントを連結します。
ヘッダーを作成する
長さが\(\mathrm{DerivedKeySize}\) の均一なランダム文字列 \(\mathrm{Salt}\) と、長さが 7 の均一なランダム文字列 \(\mathrm{NoncePrefix}\)を選択します。
次に、 \(\mathrm{Header} := \mathrm{len}(\mathrm{Header}) \| \mathrm{Salt} \| \mathrm{NoncePrefix}\)を設定します。ここで、ヘッダーの長さは 1 バイトとしてエンコードされます。 \(\mathrm{len}(\mathrm{Header}) \in \{24, 40\}\)に注意してください。
次に、 \(\mathrm{HkdfHashType}\)で指定されたハッシュ関数と入力 \(\mathrm{ikm} := \mathrm{KeyValue}\)、 \(\mathrm{salt} := \mathrm{Salt}\)、 \(\mathrm{info} := \mathrm{AssociatedData}\)を使用して、出力長 \(\mathrm{DerivedKeySize}\)の HKDF3 を使用します。結果は \(\mathrm{DerivedKey}\)です。
メッセージを分割する
次に、メッセージ \(\mathrm{Msg}\) は部分に分割されます。 \(\mathrm{Msg} = M_0 \| M_1 \| \cdots \| M_{n-1}\)
長さは、次の条件を満たすように選択されます。
- \(\mathrm{len}(M_0) \in \{0,\ldots, \mathrm{CiphertextSegmentSize} - \mathrm{len}(\mathrm{Header}) - \mathrm{16}\}\)。
- \(n>1\)の場合は \(\mathrm{len}(M_1), \ldots, \mathrm{len}(M_{n-1}) \in \{1,\ldots, \mathrm{CiphertextSegmentSize} - \mathrm{16}\}\)です。
- \(n>1\)の場合、 \(\mathrm{len}(M_{0}), \ldots, \mathrm{len}(M_{n-2})\) は上記の制約に従って最大長にする必要があります。
\(n\) は \(2^{32}\)文字以下にしてください。そうでないと、暗号化は失敗します。
ブロックを暗号化する
セグメント \(M_i\)を暗号化するには、 \(\mathrm{IV}_i := \mathrm{NoncePrefix}
\| \mathrm{i} \| b\)を計算します。ここで、 \(\mathrm{i}\) はビッグエンディアン エンコードの 4 バイトであり、バイト $b$ は $i < n-1$ の場合は 0x00
、それ以外の場合は 0x01
です。
次に、AES-GCM4 を使用して \(M_i\) を暗号化します。ここで、鍵は\(\mathrm{DerivedKey}\)、初期化ベクトルは \(\mathrm{IV}_i\)、関連データは空の文字列です。 \(C_i\) は、この暗号化の結果です(リンク先の AES-GCM リファレンスのセクション 5.2.1.2 の \(C\) と \(T\) の連結)。
暗号化されたセグメントを連結する
最後に、すべてのセグメントが \(\mathrm{Header} \| C_0 \| \cdots \| C_{n-1}\)として連結され、これが最終的な暗号文になります。
復号
復号は暗号化を逆にします。ヘッダーを使用して\(\mathrm{NoncePrefix}\)を取得し、暗号テキストの各セグメントを個別に復号します。
API では、ランダム アクセス(通常はファイルの末尾を検査せずにファイルの先頭へのアクセス)が許可される場合があります。これは意図された動作です。以前の暗号テキスト ブロックと残りの暗号テキスト ブロックをすべて復号しなくても、 \(C_i\)から \(M_i\) を復号できるためです。
ただし、API は、ユーザーがファイルの末尾エラーと復号エラーを混同しないように注意する必要があります。どちらの場合も、API はエラーを返す必要があります。この違いを無視すると、攻撃者がファイルを効果的に切り捨てられる可能性があります。
キーのシリアル化と解析
「Tink Proto」形式でキーをシリアル化するには、まず、aes_gcm_hkdf_streaming.proto で指定されているプロトコルにパラメータを明示的にマッピングします。フィールド version
は 0 に設定する必要があります。次に、通常のプロト シリアル化を使用してこれをシリアル化し、生成された文字列を KeyData プロトタイプのフィールドの値に埋め込みます。type_url
フィールドを type.googleapis.com/google.crypto.tink.AesGcmHkdfStreamingKey
に設定します。次に、key_material_type
を SYMMETRIC
に設定し、これをキーセットに埋め込みます。通常、output_prefix_type
は RAW
に設定します。例外は、output_prefix_type
に異なる値が設定されたキーが解析された場合、Tink が RAW
または以前の値を書き込む場合があります。
キーを解析するには、上記のプロセスを逆にします(プロトコルを解析する場合の通常の方法で)。フィールド key_material_type
は無視されます。output_prefix_type
の値は無視できます。また、output_prefix_type
が RAW
と異なるキーは拒否できます。version
が 0 と異なる鍵は拒否する必要があります。
既知の問題
上記の暗号化関数の実装は、フォークセーフであるとは想定されていません。フォークの安全性をご覧ください。
参照
-
Hoang, Reyhanitabar, Rogaway, Vizar, 2015. オンライン認証暗号化と、そのノンスの再利用による不正使用に対する耐性。CRYPTO 2015。https://eprint.iacr.org/2015/189 ↩
-
Hoang、Shen、2020 年。Google の Tink ライブラリでのストリーミング暗号化のセキュリティ。https://eprint.iacr.org/2020/1019 ↩
-
RFC 5869 の詳細がHMAC ベースの抽出拡張鍵導出関数(HKDF)。https://www.rfc-editor.org/rfc/rfc5869 ↩
-
NIST SP 800-38D。ブロック 暗号の動作モードに関する推奨事項: ガロア/カウンタモード(GCM)と GMAC。https://csrc.nist.gov/pubs/sp/800/38/d/final ↩