C++Builder使いじゃないのかよ。そういうツッコミは無しで。
たまたま仕事でVisual Studio 2010を使うことがあり、C#を試してみたら、これは使いやすい! 便利!と、いまさらながらに感動してしまいました。いつぞやの、VB、VC++で苦労していた時代はなんだったのかと思うほど。
その流れで、暗号化もできないかな?と個人的に調べてみると、意外にライブラリがそろっていて、とても使いやすいんですね。暗号アルゴリズムを意識することなく容易に扱えます。
こんな簡単にAESが使えるとは。アタッシェケースをつくるの、大変だったのに!(笑)
ただ、ググってみてると、意外とファイルを正しく暗号化するサンプルが落ちていないようです。そこで、試しに簡単なものを実装してみました。詳しい解説も加えてみます。
なお、記事の最後に、プロジェクトファイル一式を置いておきますので、ダウンロードしていろいろお試しいただくこともできます。
まずはサンプルに、暗号化関数です。引数には、元ファイルパスと、出力ファイルパス、パスワード文字列を指定します。
[csharp]
//———————————————————————-
//暗号化する
//———————————————————————-
public static void Encrypt(string filepath, string outfilepath, string password)
{
//AesCryptoServiceProviderオブジェクトの作成
System.Security.Cryptography.AesCryptoServiceProvider
aes = new System.Security.Cryptography.AesCryptoServiceProvider();
//※AESはブロックサイズ、キー長ともに128bit
aes.BlockSize = 128; //ブロックサイズ
aes.KeySize = 128; //キー最大長
aes.Mode = System.Security.Cryptography.CipherMode.CBC; //CBCモード
aes.Padding = System.Security.Cryptography.PaddingMode.PKCS7; //パディングモード
//初期化ベクトルの設定と取得(ブロックサイズと同サイズ=128bit)
aes.GenerateIV();
byte[] bytesIV = aes.IV;
//パスワードを適切な書式に処理する
byte[] bytesPassword = System.Text.Encoding.UTF8.GetBytes(password);
byte[] bytesKey = new byte[16]; //キー長128bit
//有効なキーサイズになっていない場合は調整する
for (int i = 0; i < 16; i++)
{
if (i < bytesPassword.Length)
{
bytesKey[i] = bytesPassword[i];
}
else
{
bytesKey[i] = 0; //余白はゼロで埋める
}
}
aes.Key = bytesKey; //処理済みパスワードをセット
//AES暗号化オブジェクトの作成
System.Security.Cryptography.ICryptoTransform encrypt = aes.CreateEncryptor();
//FileStreamの生成
System.IO.FileStream outfs = new System.IO.FileStream(
outfilepath, System.IO.FileMode.Create, System.IO.FileAccess.Write);
//IVを先頭に書き込む(128bit=16bytes)
outfs.Write(bytesIV, 0, 16);
//CryptoStreamの作成
System.Security.Cryptography.CryptoStream
cs = new System.Security.Cryptography.CryptoStream(
outfs, encrypt, System.Security.Cryptography.CryptoStreamMode.Write);
//暗号化データを書き出していく
System.IO.FileStream fs = new System.IO.FileStream(
filepath, System.IO.FileMode.Open, System.IO.FileAccess.Read);
byte[] buffer = new byte[1024];
int len;
while ((len = fs.Read(buffer, 0, buffer.Length)) > 0)
{
cs.Write(buffer, 0, len);
}
//閉じる
fs.Close();
cs.Close();
encrypt.Dispose();
outfs.Close();
}
//———————————————————————-
[/csharp]
暗号化の準備自体は、各オブジェクトを作っていけば良いですが、
やや敷居が高いところは、暗号化部分のお作法だと思います。
順を追って説明します。
アメリカ国立標準技術研究所(NIST)によれば、AESはブロックサイズ、キー長ともに、128bitと定義されていますので、
[csharp]
aes.BlockSize = 128;
aes.KeySize = 128;
[/csharp]
とします。
これ以外の値にしてもいいですが(セキュリティ上も問題ありませんが)、厳密にはAESとは言えなくなります。その場合、純粋にRijndaelを使っている、となります。
また、暗号化モードはさまざまありますが、ファイルの場合、処理速度と強度のバランスを取って、CBCモードを選択するのがベストです。
これに関しては、暗号の大家であるブルース・シュナイアー氏がその著書『暗号技術大全』の中でも語っています。
[csharp]
aes.Mode = System.Security.Cryptography.CipherMode.CBC;
[/csharp]
これは、ブロック暗号アルゴリズムでよく使われる手法です。初期化ベクトル(Initialization Vector=IV)を作って、それを元にデータ全体を混ぜ合わせるように暗号化していくイメージです。CBCモードで暗号化するときには不可欠です。
IVも自動で生成してくれる関数まで用意されています(超べんり)。
[csharp]
//初期化ベクトルの設定と取得(ブロックサイズと同サイズ=128bit)
aes.GenerateIV();
byte[] bytesIV = aes.IV;
[/csharp]
上記のコード部分では、生成したIVを取得して、暗号化ファイルに含める処理をしています。
基本的にIVは、毎回作られるデータ列が同じにならないようにするのが主目的ですので、IV自体を暗号化データにそのまま付加することには、何ら問題はありません。
たまにパスワードとは別に、IVを入力させるようなサンプルにお目にかかりますが、その必要性は、あまりありません。プログラム内で処理してしまうのがユーザーにとっては簡便だと思います。
また、下図にあるようなECBモードによる暗号ですと、元データの先頭や末尾に、同じデータ列が現れる確率が高いので(日付などのヘッダ情報や、終端データなど)、攻撃者はそれを元に、攻撃を試みます。それらを隠蔽する意味でも、先頭がランダムなIVであることには大きな意味があります。
パディングモードですが、PKCS7を選択しています。
[csharp]
aes.Padding = System.Security.Cryptography.PaddingMode.PKCS7;
[/csharp]
AESのようなブロック暗号は、決められたサイズ分のブロック毎に処理が行われるため、どうしても端数が生まれます。そこをどういうデータで埋めるのかを指定します。
ゼロで埋める(ゼロパディング)という方法もありますが、データ列の末尾がゼロで終わっているなどの場合、データの境界線が分からなくなるため、あまりおすすめできません。
ここでは、PKCS7という手法を使います。
埋められるデータには規則があり、たとえば以下のデータの場合、
データ : FF FF FF FF FF FF FF FF FF
PKCS7 の埋め込み : FF FF FF FF FF FF FF FF FF 07 07 07 07 07 07 07
と、端数部分が埋められます。
とはいえ、復号のときに、このパディングを意識する必要はありません。.NET Fameworkが処理してくれるため、プログラマはモードを指定するだけです。
次に復号ですが、暗号化とは逆の操作を行います。
暗号化処理とのちがいとしては、先頭に格納されたIVを取り出してから、復号していきます。
[csharp]
//———————————————————————-
// 復号する
//———————————————————————-
public static void Decrypt(string filepath, string outfilepath, string password)
{
//AesCryptoServiceProviderオブジェクトの作成
System.Security.Cryptography.AesCryptoServiceProvider
aes = new System.Security.Cryptography.AesCryptoServiceProvider();
//※AESはブロックサイズ、キー長ともに128bit
aes.BlockSize = 128; //ブロックサイズ
aes.KeySize = 128; //キー最大長
aes.Mode = System.Security.Cryptography.CipherMode.CBC; //CBCモード
aes.Padding = System.Security.Cryptography.PaddingMode.PKCS7; //パディングモード
//パスワードを適切な書式に処理する
byte[] bytesPassword = System.Text.Encoding.UTF8.GetBytes(password);
byte[] bytesKey = new byte[16]; //キー長128bit
byte[] bytesIV = new byte[16];
//有効なキーサイズになっていない場合は調整する
for (int i = 0; i < 16; i++)
{
if ( i < bytesPassword.Length )
{
bytesKey[i] = bytesPassword[i];
}
else
{
bytesKey[i] = 0;
}
}
aes.Key = bytesKey; //パスワード
//暗号化データを読み込んでいく
System.IO.FileStream fs = new System.IO.FileStream(
filepath, System.IO.FileMode.Open, System.IO.FileAccess.Read);
//IVを先頭から取り出してAesCryptoServiceProviderオブジェクトにセット
fs.Read(bytesIV, 0, 16);
aes.IV = bytesIV;
//AES 復号オブジェクトの作成
System.Security.Cryptography.ICryptoTransform encrypt = aes.CreateDecryptor();
//FileStreamの生成
System.IO.FileStream outfs = new System.IO.FileStream(
outfilepath, System.IO.FileMode.Create, System.IO.FileAccess.Write);
//CryptoStreamの作成
System.Security.Cryptography.CryptoStream
cs = new System.Security.Cryptography.CryptoStream(
outfs, encrypt, System.Security.Cryptography.CryptoStreamMode.Write);
byte[] buffer = new byte[1024];
int len;
while ((len = fs.Read(buffer, 0, buffer.Length)) > 0)
{
cs.Write(buffer, 0, len);
}
//閉じる
fs.Close();
cs.Close();
encrypt.Dispose();
outfs.Close();
}
//———————————————————————-
[/csharp]
以上です。
参考までに、Visual Studio 2010 Express版で作ったプロジェクトファイル一式を公開しておきます。Express版だけですが、動作は確認しています。実行ファイルも付いてますので、いろいろお試しください。
例によって、ソースは無保証の代わりに、特に使用条件はありません。
改造、転載はご自由にどうぞ。
このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください。
日々の開発作業で気づいたこと共有を。同じところで躓いている人が、 検索で辿り着けたら良いな、というスタンスで記事を書くので不定期更新になります。
コメントする