target="_blank">自分のブログ</a>でも書いたので、Qiitaでの再掲ともなりますが、そのときよりも少しだけ発展させ、かつ詳説を加えてみたいと思います。コメント欄からのご指摘がいくつかあり、検証や追記、ソースコードの修正を行いました。コメント欄から、さらなるご指摘がいくつかあり、説明やソースコードの記述の修正を行いました。`まず「暗号化」するための常識、というか、ある程度の定石について解説します。次に実際にソースコードを交えてファイルを暗号化してみます。標準で暗号化に必要なクラスがそろっていますので、今回は、それらを駆使して、自分のコードは必要最低限で済ませてみましょう。暗号化には定石がある意外とこのポイントを知らずに、ただ暗号化クラスを使っている方も多いと思います。「定石」とは、暗号アルゴリズムは何を使うか?といったレベルではなく、もっと上のレイヤー、「暗号化とは?」に近い、基礎的な手法を指します。operation)」と言います。暗号化モードとは?基本的に、どの暗号アルゴリズムを使うかに限らず、それが「ブロック暗号方式」であるのなら、まず「暗号化モード」は、何を使うのか?を検討しましょう。わからず、暗号化でやってしまいがちなのは、「ECBモード」でしょうか。![ecb_mode.png](https://qiita-image-store.s3.amazonaws.com/0/30945/4bcd15b9-c247-430a-79b8-6b2bd0c3e874.png)これの何がダメなのでしょうか?このモードでは、同じデータ、同じパスワードだと、毎回同じ暗号化データが生成されてしまうことが問題になります。「毎回同じデータが生成される」ということは、別のデータと比較することで、悪意ある攻撃者に、少なからず「ヒント」を与えることになります。同じブロックが生成されるなら、それが生成されるまでパスワードの総当り攻撃がしやすくなります。ファイル全体ではなく、小さなブロックデータなら、解析が容易になるということです。毎回同じパスワードと同じデータなど、そんなにあるわけない、と思われるかもしれません。日付など同じヘッダ情報が付加されるデータや、同じく終端データなどはどうでしょうか。ユーザーや開発者が意図しなくとも、知らずに同じデータが入ってしまうこともあるのです。逆に言えば、同じデータ、同じパスワードでも、毎回暗号化するたびに中身が変われば、攻撃者にヒントを与えにくくするということになります。そこで出てくるのが、別の暗号化モードです。主要なものはそろっています。列挙体</strong>http://msdn.microsoft.com/ja-jp/library/system.security.cryptography.ciphermode(v=vs.110).aspxとはいえ、どのモードにも、メリット、デメリットがあります。その中で、暗号強度、コストパフォーマンスのバランスから、<strong>CBCモード</strong>を選択するのがベターでしょう。暗号化の前に、初期化ベクトル(IV)を与えることで、縄のように、データを交互にねじり合せていくイメージです。![cbc_mode.png](https://qiita-image-store.s3.amazonaws.com/0/30945/38b2af20-0e91-789f-52a6-623455330387.png)暗号化する度にランダムのデータ列が与えられることで、ファイル全体が、毎回ちがう値になることが保証されます。このモードは、最初に与えるIVの質さえ考慮していれば良く、安全に暗号化を行うことができます。ファイルを暗号化するのであれば、CBCモードがベストだろう。このモードを使えば、セキュリティは大きく向上するし、保存したデータに多少エラーが発生しても、同期エラーが発生することはまずない。アプリケーションが(ハードウェアではなく)ソフトウェアベースであれば、CBCがほぼ確実にいちばんいい。ブロック暗号における「パディング」とは?これも、ブロック暗号を行うときに避けては通れないポイントです。AESでは、128bit(16バイト)と決められたデータサイズで暗号化されます。16バイトで割り切れないデータサイズのファイルを暗号化するときには、「余り」ができてしまうことがあります(もちろんピッタリの場合もありますが)。暗号化するときには、特に大きな問題は起きませんが、実は、復号するときに問題となります。元のデータと、暗号化データの境界線がわからなくなるからです(元ファイルのサイズがわからなくなる、とも言えます)。そこで、ブロック暗号には、「暗号化モード」とは別に、「パディングモード」というものも指定する必要があります。Frameworkでは、いろいろ用意されおり、自前で実装する必要がありません。使うことにしましょう。以下の例ですと、データ長が8バイトで、実際のデータ列が9バイトあれば、残りの7バイトは、以下のように埋められます。![padding_mode.png](https://qiita-image-store.s3.amazonaws.com/0/30945/2c842534-306f-5617-f4b3-0e2320d76eb6.png)つまり「余り」に埋められた合計サイズが、数値として埋められるというわけです。これにより、復号時に、データ境界線をプログラムで判別できるようになります。AESとは?さて、本題の「AES」についても少し説明をしておきましょう。target="_blank">NIST</a>)による、厳しい選定審査によって選ばれた、オープンな暗号アルゴリズムです。完全にイコールでない理由は、後述します。米国産とはいえ、言葉どおり、世界のスタンダードになりつつあります。今のところ、大きな脆弱性もなく、世界各国、いろいろな場所での採用が進んでいます。共通鍵暗号方式を採用するときは、ほぼ「AES」の一択でしょう。独自暗号じゃダメなのか?という議論もあるでしょう。原則的には推奨できません。AESは、アルゴリズムが完全にオープンにされ、多くの数学者や暗号研究家たちの検証に晒された上で耐え、最終選定されています。それは数学的にも、アルゴリズム的にも、脆弱性が「今のところない」と、公に証明されていることになります。保証されている、とも言い換えられます。独自アルゴリズムだと、それがないため、未知の脆弱性が多く潜んでいる可能性を否定できません。万が一、AESに脆弱性が発見されても(そんな事態になったら世界的な危機ですが・・・)、知名度の高さから、ニュースとしてすぐに報じられ、対応策の情報も即座に得やすいでしょう。前置きが長くなりましたが、実際にC#のコードにしてみましょう。aes.IV);```初期化ベクトルをつくるときに、さらっと「RNGCryptoServiceProvider」を使っていますが、これは、乱数でも、質の高いものを生成するためのクラスです。Generator)、すなわち「暗号乱数ジェネレータ」を使うことで、IVが攻撃者によって推測され、同じものを発生させにくくしています。Frameworkですでに用意されています。これで暗号化の準備は整いました。暗号化パスワードについてコメント欄より、[suzukis](http://qiita.com/suzukis)さんからのご指摘があり、以下の書き方だと、「鍵空間が小さくなるのではないか」とのご指摘をいただきました。bufferKey;```たしかに、この書き方ですと、たとえば16文字までパスワード文字列を指定できるのに、「a」と一文字だけにしてしまうと、15文字は空いてしまうということになります。![key_space.png](https://qiita-image-store.s3.amazonaws.com/0/30945/f5c16176-93d5-4e1e-c916-0b2d1ed61bd0.png)そこで、入力されたパスワードからランダムな文字列を生成して、きちんと16バイト埋めた状態で暗号化した方がより安全ではないか、ということでした。クラスを使うことで簡単に実現することができます。deriveBytes.GetBytes(16);```ただ、この場合、ランダムな文字列を生成した際の、「salt」をファイルに保持しておかないと(書き込んでおかないと)、復号できなくなるので、その点は注意が必要です。「AES」と定義しています。Introductionhttp://csrc.nist.gov/publications/fips/fips197/fips-197.pdfこちらも、[suzukis](http://qiita.com/suzukis)さんからのコメントでのご指摘があり、前の記事にあった「ブロックサイズ、キー長ともに128bitである」という記述を修正しました。-ただ、以前はたしか、両方とも128bitだったような・・・ひょっとすると改訂が入ったのかもしれません。-ですので、キー長とブロックサイズに、これら以外の値を設定すると、AESではないので、このクラスではエラーを吐きます。クラスを使いましょう。そのときは、"AesManaged"の部分を置換するだけです。256;```ただ、この場合、厳密には「AESを使っている」とは言えなくなります。「Rijndaelを使っている」となります。※これは定義的な問題で、中身に問題があるわけではありません。開発依頼された顧客に対して、暗号化内容を説明するときに注意が必要ということです。実は、暗号化前に圧縮をかけるというも、定石とされています。・・・と、前の記事では書いていたのですが、こちらも[suzukis](http://qiita.com/suzukis)さんからのコメントでのご指摘で、ちゃんと調べてみました。暗号分析は、平文の冗長さを利用する。暗号化の前に圧縮しておけば、冗長さが減らすことができる。暗号化は時間のかかる作業だ。暗号化の前に圧縮しておけば、作業時間を短縮できる。僕は前の記事で、「たいていは、暗号アルゴリズムの処理負荷を下げます」と書きましたが、実際に計測してみました。1000MBデータで、圧縮有無で、10回ずつ試行し、その平均値を求めてみました。|結果は圧倒的に「圧縮無し」の勝利。いうわけで、[suzukis](http://qiita.com/suzukis)さんのご指摘通りでした。ありがとうございます!とはいえ、処理時間の短縮としては、期待できませんが、平文の冗長さを無くしておくという意義からも、圧縮をやっておきましょう。すべてStream派生クラスですので、親和性が高く、合わせて使えば、ほとんどプログラマーはするべきことがありません。Framework様々ですね(笑)。サンプルコード一応、そのまま貼り付けて使えるように、以下に、関数化したサンプルソースコードも載せておきましょう。(true);}```次に復号する場合ですが、暗号化とまったく逆のことをすれば良いだけです。暗号化では、<strong>先に</strong>圧縮していたので、<strong>復号後</strong>に、解凍処理という順番となっています。"復号に失敗しました。(true);}```ここで注意していただきたいのは、一度暗号化してしまうと、ファイルを復号しても、ファイル情報が失われてしまうことです。ファイル情報とは、ファイル名、属性、タイムスタンプなどです。した情報も正常に戻せるようにするには、暗号化ファイルにそのデータも含めるなど、少々複雑な処理が必要になってくるでしょう。パスワードが合っているのか、間違っているのかの判定もしていません。このソースコードでは、誤ったパスワードで復号すると、さらに変なデータ配列に混ぜられてしまいます(おそらく元のデータに戻せなくなります)。これについても、復号が成功したか、ヘッダに何かしらの印を埋め込んでおいて、それでパスワードの成否を判定する必要があります。今回は、「ファイルを暗号化する」という部分に焦点を合わせて書きましたので、そういった「完全版」は、また別の機会に取り上げたいと思います。一応、GitHubにも今回のプロジェクトファイルごと上げました。Desktopでの動作、ビルドを確認済みです。ちなみに、MITライセンスです。