Lazy Diary @ Hatena Blog

PowerShell / Java / miscellaneous things about software development, Tips & Gochas. CC BY-SA 4.0/Apache License 2.0

ファイルをAESで暗号化したときに送信先とnonceを共有する方法

AESをECBモードで使っていたり、IVとして固定値を使っていたりするケースを避けるお手本として

  • あるファイルをAESで暗号化し、ローカルマシンのストレージ上に保存しておく
  • 暗号化したファイルをリモートマシンへ送付し、リモートマシンのストレージに保存する
  • あるタイミングでリモートマシン上でファイルを復号する

という処理を実装しようとしたときに、AESで使うIVとかnonceをどう生成し、ローカルマシンとリモートマシンでどう共有するか?という話をまとめておく。なお、以降では話を簡単にするためAESはCTRモードで使用する(nonceを使う)ものとする。nonceは再利用を避けられればそれでよい。nonceの値は推測可能でもよい *1

nonceの生成方法

NIST SP 800-38A *2 では、nonceおよびIVの作成方法として以下の(A)(B)2つの方法が挙げられている。またRFC 5297 *3 ではnonceとしてカウンタを使う方法の他に、タイムスタンプを使う方法(C)が提示されている。

  • (A) カウンタを暗号化する*4
  • (B) FIPS 140-2準拠の乱数を使う
  • (C) タイムスタンプを使う

まず、nonceの生成のためにカウンタを保存する方法(A)は実装や管理の煩雑さから避けたい。末番を保存しておいてそれをカウントアップする方法は、どこに末番を保存するか?末番の採番を並列化可能にするにはどうするか?といった検討が必要になってしまう*5

方法(B)と(C)に大きな違いはないが、(B)には誕生日のパラドックスに起因してnonceの衝突確立が(C)より高いという問題がある。また、完全に重複を避けるには使った乱数をすべて保存しておく必要がある。そのため、ここではいったん(B)を除外する。

ここでは(C)を利用することにする。たとえばシステムクロックのミリ秒までの値をnonceとして利用する。Java 9以降であれば、Instant.now()でシステムクロックを取得すれば、getEpochSecond()とgetNano()でナノ秒単位の時刻が取得できる。システムクロックは一方向にしか進まないため、ある時点でnonceの生成処理が複数同時に実行されることさえ防げば、nonceが再利用されないことを担保できる。

nonceの共有方法

ファイルの復号のため、nonceはローカルマシンとリモートマシンとで共有する必要がある。ここで、ファイルをリモートマシンへ送付する際、nonceを別ファイルに格納して送るのは必要な処理が増えるし、処理の原子性の考慮が難しくなるため避けたい。

ローカルマシンとリモートマシンのシステムクロックの値をnonceとして利用することで、ローカルマシンからリモートマシンへnonceを送信せずにnonceを共有できるのでは?とも考えたけど、暗号化するタイミングと復号するタイミングは異なるはずなので、そもそもこの方法は使えない。仮に使えたとしても、どうしてもローカルマシンとリモートマシンとでnonceの値がズレるタイミングが発生する *6 。また、同時刻に実行された処理で同一のnonceが使われるのを防ぐことも難しい。

nonceはファイルごとに決める必要があり、またファイル間でnonceの値は異なっている必要がある。これはファイル名の特性と同じである *7。そのため、ファイル名にnonceの値を含めて送信することで、ローカルマシンとリモートマシンとでnonceを共有することができる。

nonceの一意性を担保する方法

nonceを生成して暗号化されたファイルを作る際、もしnonceと同じ名前のファイルがすでに存在していたら、そのnonceは使用済ということになる。その場合は再度nonceを取得すればよい。

*1:暗号強度は鍵のビット長で担保される

*2:https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf

*3:https://tools.ietf.org/html/rfc5297

*4:カウンタが暗号ブロック長より短かければECBモードで暗号化しても問題にならない

*5:テストの再現性の面では利点もあるが、それはnonceの生成処理をinject可能にしておけばよい話で、カウンタが必須なわけではない

*6:たとえばYYYYMMDDHHMMSSをnonceにするとして、ローカルマシンの時刻が2020/01/01 01:01:01.999、リモートマシンの時刻が2020/01/01 01:01:02.001とすると、NTPによる時刻同期の精度としては上出来だがnonceとしてはローカルマシンとリモートマシンでズレてしまっているので役に立たないことになる

*7:ファイル名はファイルごとに決める必要があり、同名のファイルは保存できない