ezio の暗号化について
このドキュメントでは、ezio の暗号処理上の基本的な仕様を解説します。
ezio は、主に Linux/UNIX で動作するアーカイバですが、外部ツール(openssl や gpg)に依存せず、自前で暗号や署名を処理します。内部的には Go 言語が標準的に提供するライブラリを利用していますが、それらライブラリを正しく利用できているのかは、必ずしも検証されていません。
ezio の format
ezio は file を EZML format で encode します。EZML は、Matroska や WebM で採用されている EBML のサブセットとして設計されました(たとえば element type は master, binary, uint64 のみです)。EBML は XML のバイナリー版といわれることもあるフォーマットで、拡張が比較的に容易です。
ezio が file を暗号化すると、XML 風な表現をするなら、出力は以下のような構造となります。
<Ezml>
<Suite>cipher algorithm</Suite>
<KekSalt>random byte</KekSalt>
<Nonce>random byte + counter^NonceMask</Nonce>
<NonceMask>random byte</NonceMask>
<Cek>encrypted CEK</Cek>
<MetaData>encrypted metadata</MetaData>
<Data>encrypted data chunk</Data>
<Data>encrypted data chunk</Data>
...
<MetaData>encrypted metadata</MetaData>
<Eof>total size</Eof>
</Ezml>
ちなみに、暗号化しない場合は以下のように。
<Ezml>
<Suite>cipher algorithm</Suite>
<Path>filename</Path>
<Type>file type</Type>
<Size>file size</Size>
...
<Data>data chunk</Data>
<Data>data chunk</Data>
...
<SizeEnc>encoded file size</SizeEnc>
<Hash>file data hash</Hash>
<HashMeta>metadata hash</HashMeta>
<Eof>total size</Eof>
</Ezml>
ezio は、file ごとに以上のような構造へ encode した後、単純に archive file へ追記していきます。
ただし、archive の最後には、quick access 用の metadata list や、archive repairing 用の erasure code などの、特別なデータが付加されることがあります。しかし、それらは file の抽出に必要というわけではなく、個々の file は上記の構造で局所的に完結しています。
暗号化に関連する element
Suite
圧縮のアルゴリズムとともに、どの暗号のアルゴリズム(AES256-GCM もしくは CHACHA20-POLY1305)、Hash 関数(SHA256 もしくは SHA512)が使われているか、記録されています。
KekSalt
Go の crypto/rand.Read() によって生成された 32 もしくは 64 bytes の乱数です(以下、乱数は全て crypto/rand.Read() によります)。ユーザーが入力した password による対称鍵暗号を使用するモードでのみ、生成されます。
この salt と password を使い、sha256 もしくは sha512 の HKDF によって、KEK(Key Encryption Key) を生成します。
KekSalt は、言い換えると KEK は、1 つの archive の中の全ての file で共通であるように実装されていますが、そうしなければならない理由はありません。しかし file ごとに KEK を変更する必然性も(おそらく)ないはずです。
Nonce
AES256-GCM もしくは CHACHA20-POLY1305 で使用する 12 bytes の nonce(IV) の初期値です。
Nonce は、2 つの部分から構成されます。random part 4 bytes と counter part 8 bytes です。random part は crypt/rand.Read() によって生成された乱数です。counter part は、uint64 の counter と NonceMask の xor です。
MSB | 0| 1| 2| 3| 4| 5| 6| 7| 8| 9| 10| 11| LSB
| random | counter xor NonceMask |
counter は、Nonce を暗号化/複合化で使うたびに +1 します。上記 XML 風表現の場合、Cek が最初の encrypted element ですが、その暗号化の際にはNonce の counter を increment して生成した nonce を使用し、次の MetaData の暗号化の際には、さらに increment したものを使うわけです。
encrypt された binary の先頭 12 bytes には、使用した nonce が埋め込まれています。復号の際には、埋め込まれた nonce と、Nonce を初期値として生成した nonce を比較して、一致していることを確認します。
nonce が counter によって生成されることから、1 つの file の暗号化において、同じ値の nonce が重複して使用されることはありません。また、archive 内の全ての file の暗号化においても nonce が unique となるよう、counter と NonceMask は次の file の処理へ持ち越します。
NonceMask
乱数 8 bytes です。
Cek
Cek element は、対称鍵暗号モードの場合は KEK を使った AES256-GCM/CHACHA20-POLY1305 によって、公開鍵暗号モードの場合は public key を使った RSA によって暗号化された CEK(Contents Encryption Key) です。
CEK そのものは、必要な大きさ(現時点では 32 bytes)の乱数です。
CEK は、KEK とは違って、file ごとに別の値をとります。このため、公開鍵暗号モードでは file ごとの RSA 秘密鍵での復号処理が重く、結果として archive の extract が非常に遅い問題を抱えています。(file data そのものを RSA で処理するのではなく、
あくまでも CEK のみ公開鍵暗号で処理するにも関わらず、かなり重いです)
なお、AES256-GCM や CHACHA20-POLY1305 で暗号化された binary は、先頭 12 bytes が nonce です。その後に暗号化された本体が続きます。そして最後に 16 bytes の tag が付加されています。
MetaData
MetaData element は encrypt された binary ですが、論理的には(XML 風に書くなら)以下のような構造をしています。
<MetaData>
<Path>dir/name</Path>
<Size>13539</Size>
</MetaData>
<Path>
などの child element を EZML で encode した後に、すべてまとめた一塊の binary を、CEK と nonce を使って AES256-GCM/CHACHA20-POLY1305 で暗号化したものが、MetaData element の binary となります。
なお、MetaData は圧縮されません。
Data
Data は file の内容そのものです。圧縮する場合は、暗号化の前に行います。
AES256-GCM や CHACHA20-POLY1305 の処理上の都合により、(compressed) data は 16MiB 以下の大きさの chunk に分割されて、CEK と nonce を使って暗号化されます。
(暗号化されなくても必ず分割されます)
16MiB というサイズに論理的な根拠はなく、議論の余地があります。