みなさん、人に見られたくないデータのひとつやふたつあるのではないでしょうか。そこで今回は暗号化に挑戦することにします。C#ならファイルの暗号化も簡単にできてしまいます。
暗号には共通鍵暗号と公開鍵暗号があります。共通鍵暗号は対称鍵暗号とも呼ばれ、暗号化と復号に同一の(共通の)鍵を用いる暗号方式です。それに対して公開鍵暗号は暗号化と復号化のときには別の鍵を使います。今回扱うのは共通鍵暗号です。
共通鍵暗号にはストリーム暗号とブロック暗号があります。ビットやバイト単位で暗号化するのではなく、決まった長さのデータをブロックにして暗号化していきます。いまではブロック暗号の方が一般的です。
Contents
AES暗号
そのなかでも強力な暗号がAESです。AESはAdvanced Encryption Standardの略で、アメリカ国立標準技術研究所(NIST)の主導により公募され採用された暗号方式なのです。
AESの鍵長は3種類あり、128ビット、192ビット、256ビットのいずれかです。ブロック長は128ビットの1種類のみです。
暗号化のためにはAesManagedクラスを使います。そして暗号鍵と鍵のサイズ、暗号利用モードを決めます。暗号利用モードはECBとCBCがありますが、CBCが一般的で、ECBは非推奨です。
なぜか? 同じデータを同じ暗号鍵で暗号化した場合、同一の暗号パターンとなってしまうからです。文章の場合、決まり切った書き出しになっている場合、それが解読を試みる者にとってヒントになってしまうからです。
ところがCBCは同じデータを同じ暗号鍵で暗号化しても同一パターンにはなりません。ブロックごとに暗号化の処理をしていくのですが、そのときに1つ前の処理結果を利用します。初期化ベクトルを変えることで暗号化されたデータを変えることができるのです。
ブロックごとの処理において1つ前の処理結果を利用する。では一番最初はどうするのでしょうか。このときに使われる値が初期化ベクトルです。
また一定の長さのブロックごとに処理していくと最後のブロックの部分で余ったり足りない部分がでてきます。これを埋める方法として用意されているのがパディングです。
暗号化をするためには
鍵
鍵長
ブロック長
暗号利用モード
パディング
初期化ベクトル
これらを決める必要があります。
実際につくってみる
鍵長 256
ブロック長 128
暗号利用モード CBC
鍵はパスワードからつくることにします。Rfc2898DeriveBytesクラスを使うとパスワードから鍵を生成することができます。
Rfc2898DeriveBytesで鍵をつくるにはSaltが必要です。Saltは8バイト以上でなければなりません。そこでまずSaltを生成し、それを利用して暗号キーと初期化ベクトルを生成しています。復号化のときにSaltと初期化ベクトルが必要になるので暗号化されたファイルの先頭につけることにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
using System; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.IO; using System.Security.Cryptography; using System.IO.Compression; using System.Security.Cryptography; public partial class Form1 : Form { private const int KEY_SIZE = 256; private const int BLOCK_SIZE = 128; private const int BUFFER_SIZE = BLOCK_SIZE * 32; // バッファーサイズはBlockSizeの倍数にする private const int SALT_SIZE = 25; // 8以上 private static byte[] GetSalt() { Rfc2898DeriveBytes deriveBytes = new Rfc2898DeriveBytes(DateTime.Now.Ticks.ToString(), SALT_SIZE); return deriveBytes.Salt; } //パスワードから暗号キーを作成する private byte[] GetKeyFromPassword(string password, byte[] salt) { //Rfc2898DeriveBytesオブジェクトを作成する Rfc2898DeriveBytes deriveBytes = new Rfc2898DeriveBytes(password, salt); //反復処理回数を指定する デフォルトで1000回 deriveBytes.IterationCount = 1000; //キーを生成する return deriveBytes.GetBytes(KEY_SIZE / 8); } //パスワードから初期化ベクトルを作成する private byte[] GetIVFromPassword(string password) { Rfc2898DeriveBytes deriveBytes = new Rfc2898DeriveBytes(password + DateTime.Now.Ticks.ToString(), 100); deriveBytes.IterationCount = 1000; return deriveBytes.GetBytes(BLOCK_SIZE / 8); } } |
暗号化をするまえに圧縮をしています。平文に冗長さがあるとそのまま暗号化しても解読されやすくなります。暗号化する前に圧縮することで、この問題を解決することができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
public partial class Form1 : Form { public void EncodeFile(string srcFilePath, string destFilePath, string password) { // Saltを生成する byte[] salt = GetSalt(); byte[] key = GetKeyFromPassword(password, salt); byte[] iv = GetIVFromPassword(password); using (AesManaged aes = new AesManaged()) { // AESインスタンスのパラメータ設定 aes.KeySize = KEY_SIZE; aes.BlockSize = BLOCK_SIZE; aes.Mode = CipherMode.CBC; aes.Key = key; aes.IV = iv; aes.Padding = PaddingMode.ISO10126; // 暗号化オブジェクト生成 ICryptoTransform ct = aes.CreateEncryptor(aes.Key, aes.IV); // 出力ファイルストリーム using (FileStream outFileStream = new FileStream(destFilePath, FileMode.Create, FileAccess.Write)) { // Saltの書き込み outFileStream.Write(salt, 0, SALT_SIZE); // 初期化ベクトル 書き込み outFileStream.Write(aes.IV, 0, BLOCK_SIZE / 8); using (CryptoStream cryptoStream = new CryptoStream(outFileStream, ct, CryptoStreamMode.Write)) { // Deflateアルゴリズムを使用した圧縮 using (DeflateStream deflateStream = new DeflateStream(cryptoStream, CompressionMode.Compress)) { using (FileStream inFileStream = new FileStream(srcFilePath, FileMode.Open, FileAccess.Read)) { byte[] buf = new byte[BUFFER_SIZE]; while (true) { int size = inFileStream.Read(buf, 0, buf.Length); if (size == 0) break; // 出力ファイルへ書き込み(圧縮→暗号化→書き込み) deflateStream.Write(buf, 0, size); } } } } } } return; } public void DecodeFile(string srcFilePath, string destFilePath, string password) { using (AesManaged aes = new AesManaged()) { // AESインスタンスのパラメータ設定 aes.KeySize = KEY_SIZE; aes.BlockSize = BLOCK_SIZE; aes.Mode = CipherMode.CBC; aes.Padding = PaddingMode.ISO10126; using (FileStream inFileStream = new FileStream(srcFilePath, FileMode.Open, FileAccess.Read)) { // Saltを読み込む byte[] salt = GetSalt(); inFileStream.Read(salt, 0, SALT_SIZE); byte[] key = GetKeyFromPassword(password, salt); aes.Key = key; // 初期化ベクトル読込 byte[] iv = new byte[BLOCK_SIZE / 8]; inFileStream.Read(iv, 0, iv.Length); aes.IV = iv; // 復号化オブジェクト生成 ICryptoTransform ct = aes.CreateDecryptor(aes.Key, aes.IV); using (CryptoStream cryptoStream = new CryptoStream(inFileStream, ct, CryptoStreamMode.Read)) { // Deflateアルゴリズムを使用して圧縮解除 using (DeflateStream ds = new DeflateStream(cryptoStream, CompressionMode.Decompress)) { using (FileStream outFs = new FileStream(destFilePath, FileMode.Create, FileAccess.Write)) { byte[] buf = new byte[BUFFER_SIZE]; while (true) { int size = ds.Read(buf, 0, buf.Length); if (size == 0) break; outFs.Write(buf, 0, size); } } } } } } return; } } |
あとはこれらのメソッドを呼び出すだけです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
public partial class Form1 : Form { private void button1_Click(object sender, EventArgs e) { OpenFileDialog dialog = new OpenFileDialog(); if (dialog.ShowDialog() == DialogResult.OK) { SaveFileDialog dialog1 = new SaveFileDialog(); if (dialog1.ShowDialog() == DialogResult.OK) { EncodeFile(dialog.FileName, dialog1.FileName, textBox1.Text); } } } private void button2_Click(object sender, EventArgs e) { OpenFileDialog dialog = new OpenFileDialog(); if (dialog.ShowDialog() == DialogResult.OK) { SaveFileDialog dialog1 = new SaveFileDialog(); if (dialog1.ShowDialog() == DialogResult.OK) { DecodeFile(dialog.FileName, dialog1.FileName, textBox1.Text); } } } } |