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 というサイズに論理的な根拠はなく、議論の余地があります。

参考リンク

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.