DOBON.NET プログラミング道: .NET Framework, VB.NET, C#, Visual Basic, Visual Studio, インストーラ, ...

DOBON.NET

ファイルを暗号化する

ここでは共有キー暗号方式 (対称暗号化方式、秘密鍵暗号)による暗号化、復号化について説明します。共有キー暗号方式では、単一の共有キーを使用してデータの暗号化と復号化が行われます。なお秘密鍵暗号に関して「アスキー デジタル用語辞典」では、次のように説明されています。

「暗号システムにおいて、暗号化に使用する鍵と復号化に使用する鍵が同じもののこと。従来の暗号システムはほとんどすべてがこれに相当するため、慣用暗号システムとも呼ばれる。鍵が1種類しかないため、暗号化に使用した鍵を、通信路とは別の安全な手段を使って、通信相手に届けておく必要がある。」

なお、NTFSファイルシステムによってファイルを暗号化する(暗号化属性を付加する)方法は、こちらで紹介しています。

テキストファイルを暗号化する

ファイルを暗号化する方法は「マイクロソフト サポート技術情報 - 307010」にて詳しく説明されています。ただしこのサンプルには2つの欠点があります。パスワードを必ず8バイトにする必要がある点と、暗号化されるファイルがUTF-8エンコーディングである必要がある(復号化の時、元のファイルがUTF-8として読み込み、UTF-8で出力している)という点です。

これではほとんど役に立たないので、この2点を修正したものを書いてみました。共有キー(秘密キー)と初期化ベクタは、与えられたパスワードが何バイトであるかにかかわらず、適切なバイト数に調節して設定しています。また、暗号化するファイルのエンコーディングはShift JISとしています。

[VB.NET]
''' <summary>
''' ファイルを暗号化する
''' </summary>
''' <remarks>
''' 暗号化されたファイルは"(ファイル名).enc"として保存される
''' </remarks>
''' <param name="fileName">暗号化するファイル名</param>
''' <param name="key">パスワード</param>
Public Shared Sub EncryptFile(ByVal fileName As String, _
                            ByVal key As String)
    '暗号化するファイルを読み込む
    Dim fsIn As New System.IO.FileStream(fileName, _
        System.IO.FileMode.Open, System.IO.FileAccess.Read)
    'すべて読み込む
    Dim bytesIn(fsIn.Length - 1) As Byte
    fsIn.Read(bytesIn, 0, bytesIn.Length)
    '閉じる
    fsIn.Close()

    'DESCryptoServiceProviderオブジェクトの作成
    Dim des As New System.Security.Cryptography.DESCryptoServiceProvider

    '共有キーと初期化ベクタを決定
    'パスワードをバイト配列にする
    Dim bytesKey As Byte() = System.Text.Encoding.UTF8.GetBytes(key)
    '共有キーと初期化ベクタを設定
    des.Key = ResizeBytesArray(bytesKey, des.Key.Length)
    des.IV = ResizeBytesArray(bytesKey, des.IV.Length)

    '暗号化されたファイルの保存先
    Dim outFileName As String = fileName + ".enc"
    '暗号化されたファイルを書き出すためのFileStream
    Dim fsOut As New System.IO.FileStream(outFileName, _
        System.IO.FileMode.Create, System.IO.FileAccess.Write)
    'DES暗号化オブジェクトの作成
    Dim desdecrypt As System.Security.Cryptography.ICryptoTransform = _
        des.CreateEncryptor()
    '書き込むためのCryptoStreamの作成
    Dim cryptStreem As New System.Security.Cryptography.CryptoStream( _
        fsOut, desdecrypt, _
        System.Security.Cryptography.CryptoStreamMode.Write)
    '書き込む
    cryptStreem.Write(bytesIn, 0, bytesIn.Length)
    '閉じる
    cryptStreem.Close()
    fsOut.Close()
End Sub

''' <summary>
''' ファイルを復号化する
''' </summary>
''' <param name="fileName">復号化するファイル名</param>
''' <param name="key">パスワード</param>
Public Shared Sub DecryptFile(ByVal fileName As String, _
                            ByVal key As String)
    'DESCryptoServiceProviderオブジェクトの作成
    Dim des As New System.Security.Cryptography.DESCryptoServiceProvider

    '共有キーと初期化ベクタを決定
    'パスワードをバイト配列にする
    Dim bytesKey As Byte() = System.Text.Encoding.UTF8.GetBytes(key)
    '共有キーと初期化ベクタを設定
    des.Key = ResizeBytesArray(bytesKey, des.Key.Length)
    des.IV = ResizeBytesArray(bytesKey, des.IV.Length)

    '暗号化されたファイルを読み込むためのFileStream
    Dim fsIn As New System.IO.FileStream(fileName, _
        System.IO.FileMode.Open, System.IO.FileAccess.Read)
    'DES復号化オブジェクトの作成
    Dim desdecrypt As System.Security.Cryptography.ICryptoTransform = _
        des.CreateDecryptor()
    '読み込むためのCryptoStreamの作成
    Dim cryptStreem As New System.Security.Cryptography.CryptoStream( _
        fsIn, desdecrypt, _
        System.Security.Cryptography.CryptoStreamMode.Read)

    '復号化されたファイルの保存先
    Dim outFileName As String
    If fileName.ToLower().EndsWith(".enc") Then
        outFileName = fileName.Substring(0, fileName.Length - 4)
    Else
        outFileName = fileName + ".dec"
    End If
    '復号化されたデータを書き出すためのStreamWriter
    Dim sw As New System.IO.StreamWriter( _
        outFileName, False, System.Text.Encoding.GetEncoding("sjis"))
    '復号化されたデータを読み込む
    Dim sr As New System.IO.StreamReader( _
        cryptStreem, System.Text.Encoding.GetEncoding("sjis"))
    '復号化されたデータを書き出す
    sw.Write(sr.ReadToEnd())
    sw.Flush()

    '閉じる
    sr.Close()
    sw.Close()
    cryptStreem.Close()
    fsIn.Close()
End Sub

''' <summary>
''' 共有キー用に、バイト配列のサイズを変更する
''' </summary>
''' <param name="bytes">サイズを変更するバイト配列</param>
''' <param name="newSize">バイト配列の新しい大きさ</param>
''' <returns>サイズが変更されたバイト配列</returns>
Private Shared Function ResizeBytesArray(ByVal bytes() As Byte, _
                            ByVal newSize As Integer) As Byte()
    Dim newBytes(newSize - 1) As Byte
    If bytes.Length <= newSize Then
        Dim i As Integer
        For i = 0 To bytes.Length - 1
            newBytes(i) = bytes(i)
        Next i
    Else
        Dim pos As Integer = 0
        Dim i As Integer
        For i = 0 To bytes.Length - 1
            newBytes(pos) = newBytes(pos) Xor bytes(i)
            pos += 1
            If pos >= newBytes.Length Then
                pos = 0
            End If
        Next i
    End If
    Return newBytes
End Function
[C#]
/// <summary>
/// ファイルを暗号化する
/// </summary>
/// <remarks>
/// 暗号化されたファイルは"(ファイル名).enc"として保存される
/// </remarks>
/// <param name="fileName">暗号化するファイル名</param>
/// <param name="key">パスワード</param>
public static void EncryptFile(string fileName, string key) 
{
    //暗号化するファイルを読み込む
    System.IO.FileStream fsIn = 
        new System.IO.FileStream(fileName, 
        System.IO.FileMode.Open, System.IO.FileAccess.Read);
    //すべて読み込む
    byte[] bytesIn = new byte[fsIn.Length];
    fsIn.Read(bytesIn, 0, bytesIn.Length);
    //閉じる
    fsIn.Close();

    //DESCryptoServiceProviderオブジェクトの作成
    System.Security.Cryptography.DESCryptoServiceProvider des =
        new System.Security.Cryptography.DESCryptoServiceProvider();

    //共有キーと初期化ベクタを決定
    //パスワードをバイト配列にする
    byte[] bytesKey = System.Text.Encoding.UTF8.GetBytes(key);
    //共有キーと初期化ベクタを設定
    des.Key = ResizeBytesArray(bytesKey, des.Key.Length);
    des.IV = ResizeBytesArray(bytesKey, des.IV.Length);

    //暗号化されたファイルの保存先
    string outFileName = fileName + ".enc";
    //暗号化されたファイルを書き出すためのFileStream
    System.IO.FileStream fsOut = 
        new System.IO.FileStream(outFileName,
        System.IO.FileMode.Create, System.IO.FileAccess.Write);
    //DES暗号化オブジェクトの作成
    System.Security.Cryptography.ICryptoTransform desdecrypt =
        des.CreateEncryptor();
    //書き込むためのCryptoStreamの作成
    System.Security.Cryptography.CryptoStream cryptStreem =
        new System.Security.Cryptography.CryptoStream(fsOut,
        desdecrypt,
        System.Security.Cryptography.CryptoStreamMode.Write);
    //書き込む
    cryptStreem.Write(bytesIn, 0, bytesIn.Length);
    //閉じる
    cryptStreem.Close();
    fsOut.Close();
}

/// <summary>
/// ファイルを復号化する
/// </summary>
/// <param name="fileName">復号化するファイル名</param>
/// <param name="key">パスワード</param>
public static void DecryptFile(string fileName, string key) 
{
    //DESCryptoServiceProviderオブジェクトの作成
    System.Security.Cryptography.DESCryptoServiceProvider des =
        new System.Security.Cryptography.DESCryptoServiceProvider();

    //共有キーと初期化ベクタを決定
    //パスワードをバイト配列にする
    byte[] bytesKey = System.Text.Encoding.UTF8.GetBytes(key);
    //共有キーと初期化ベクタを設定
    des.Key = ResizeBytesArray(bytesKey, des.Key.Length);
    des.IV = ResizeBytesArray(bytesKey, des.IV.Length);

    //暗号化されたファイルを読み込むためのFileStream
    System.IO.FileStream fsIn = 
        new System.IO.FileStream(fileName, 
        System.IO.FileMode.Open, System.IO.FileAccess.Read);
    //DES復号化オブジェクトの作成
    System.Security.Cryptography.ICryptoTransform desdecrypt =
        des.CreateDecryptor();
    //読み込むためのCryptoStreamの作成
    System.Security.Cryptography.CryptoStream cryptStreem =
        new System.Security.Cryptography.CryptoStream(fsIn,
        desdecrypt,
        System.Security.Cryptography.CryptoStreamMode.Read);

    //復号化されたファイルの保存先
    string outFileName;
    if (fileName.ToLower().EndsWith(".enc"))
        outFileName = fileName.Substring(0, fileName.Length - 4);
    else
        outFileName = fileName + ".dec";

    //復号化されたデータを書き出すためのStreamWriter
    System.IO.StreamWriter sw =
        new System.IO.StreamWriter(outFileName, false,
        System.Text.Encoding.GetEncoding("sjis"));
    //復号化されたデータを読み込む
    System.IO.StreamReader sr =
        new System.IO.StreamReader(cryptStreem,
        System.Text.Encoding.GetEncoding("sjis"));
    //復号化されたデータを書き出す
    sw.Write(sr.ReadToEnd());
    sw.Flush();

    //閉じる
    sr.Close();
    sw.Close();
    cryptStreem.Close();
    fsIn.Close();
}

/// <summary>
/// 共有キー用に、バイト配列のサイズを変更する
/// </summary>
/// <param name="bytes">サイズを変更するバイト配列</param>
/// <param name="newSize">バイト配列の新しい大きさ</param>
/// <returns>サイズが変更されたバイト配列</returns>
private static byte[] ResizeBytesArray(byte[] bytes, int newSize)
{
    byte[] newBytes = new byte[newSize];
    if (bytes.Length <= newSize)
    {
        for (int i = 0; i < bytes.Length; i++)
            newBytes[i] = bytes[i];
    }
    else
    {
        int pos = 0;
        for (int i = 0; i < bytes.Length; i++)
        {
            newBytes[pos++] ^= bytes[i];
            if (pos >= newBytes.Length)
                pos = 0;
        }
    }
    return newBytes;
}

上記の例では、共有キーと初期化ベクタを決定するために、与えられたパスワード(パスワードをバイト型配列に変換したもの)が短ければ残りを0で埋め、長ければ切り詰めるようにしています。切り詰め方としては単にカットするのではなく、切り詰める部分をXORで合成しています(果たしてこれがベストであるかは分かりませんが)。また、共有キーと初期化ベクタのサイズの決定は、DESCryptoServiceProviderのKeyとIVプロパティで取得されるバイト配列の大きさと同じとしていますが、DESCryptoServiceProvider.LegalKeySizesやLegalBlockSizesプロパティを調べて決めた方がよりよいでしょう。

バイナリファイルを暗号化する

上記の方法では暗号化されるファイルはShift JISである必要がありますが、DecryptFileを次のように変えることにより、どんなファイルでも暗号化、復号化できるようになるでしょう。

[VB.NET]
''' <summary>
''' ファイルを復号化する
''' </summary>
''' <param name="fileName">復号化するファイル名</param>
''' <param name="key">パスワード</param>
Public Shared Sub DecryptFile(ByVal fileName As String, _
                            ByVal key As String)
    'DESCryptoServiceProviderオブジェクトの作成
    Dim des As New System.Security.Cryptography.DESCryptoServiceProvider

    '共有キーと初期化ベクタを決定
    'パスワードをバイト配列にする
    Dim bytesKey As Byte() = System.Text.Encoding.UTF8.GetBytes(key)
    '共有キーと初期化ベクタを設定
    des.Key = ResizeBytesArray(bytesKey, des.Key.Length)
    des.IV = ResizeBytesArray(bytesKey, des.IV.Length)

    '暗号化されたファイルを読み込むためのFileStream
    Dim fsIn As New System.IO.FileStream(fileName, _
        System.IO.FileMode.Open, System.IO.FileAccess.Read)
    'DES復号化オブジェクトの作成
    Dim desdecrypt As System.Security.Cryptography.ICryptoTransform = _
        des.CreateDecryptor()
    '読み込むためのCryptoStreamの作成
    Dim cryptStreem As New System.Security.Cryptography.CryptoStream( _
        fsIn, desdecrypt, _
        System.Security.Cryptography.CryptoStreamMode.Read)

    '復号化されたファイルの保存先
    Dim outFileName As String
    If fileName.ToLower().EndsWith(".enc") Then
        outFileName = fileName.Substring(0, fileName.Length - 4)
    Else
        outFileName = fileName + ".dec"
    End If '復号化されたファイルを書き出すためのFileStream
    Dim fsOut As New System.IO.FileStream(outFileName, _
        System.IO.FileMode.Create, System.IO.FileAccess.Write)

    '復号化されたデータを書き出す
    Dim bs(255) As Byte
    Dim readLen As Integer
    Do
        readLen = cryptStreem.Read(bs, 0, bs.Length)
        If readLen > 0 Then
            fsOut.Write(bs, 0, readLen)
        End If
    Loop While (readLen > 0)

    '閉じる
    cryptStreem.Close()
    fsIn.Close()
    fsOut.Close()
End Sub
[C#]
/// <summary>
/// ファイルを復号化する
/// </summary>
/// <param name="fileName">復号化するファイル名</param>
/// <param name="key">パスワード</param>
public static void DecryptFile(string fileName, string key) 
{
    //DESCryptoServiceProviderオブジェクトの作成
    System.Security.Cryptography.DESCryptoServiceProvider des =
        new System.Security.Cryptography.DESCryptoServiceProvider();

    //共有キーと初期化ベクタを決定
    //パスワードをバイト配列にする
    byte[] bytesKey = System.Text.Encoding.UTF8.GetBytes(key);
    //共有キーと初期化ベクタを設定
    des.Key = ResizeBytesArray(bytesKey, des.Key.Length);
    des.IV = ResizeBytesArray(bytesKey, des.IV.Length);

    //暗号化されたファイルを読み込むためのFileStream
    System.IO.FileStream fsIn = 
        new System.IO.FileStream(fileName, 
        System.IO.FileMode.Open, System.IO.FileAccess.Read);
    //DES復号化オブジェクトの作成
    System.Security.Cryptography.ICryptoTransform desdecrypt =
        des.CreateDecryptor();
    //読み込むためのCryptoStreamの作成
    System.Security.Cryptography.CryptoStream cryptStreem =
        new System.Security.Cryptography.CryptoStream(fsIn,
        desdecrypt,
        System.Security.Cryptography.CryptoStreamMode.Read);

    //復号化されたファイルの保存先
    string outFileName;
    if (fileName.ToLower().EndsWith(".enc"))
        outFileName = fileName.Substring(0, fileName.Length - 4);
    else
        outFileName = fileName + ".dec";
    //復号化されたファイルを書き出すためのFileStream
    System.IO.FileStream fsOut =
        new System.IO.FileStream(outFileName,
        System.IO.FileMode.Create, System.IO.FileAccess.Write);

    //復号化されたデータを書き出す
    byte[] bs = new byte[256];
    int readLen;
    while ((readLen = cryptStreem.Read(bs, 0, bs.Length)) > 0)
        fsOut.Write(bs, 0, readLen);

    //閉じる
    cryptStreem.Close();
    fsIn.Close();
    fsOut.Close();
}

その他の共有キー暗号化アルゴリズムを実装するクラス

ここでは、DESCryptoServiceProvider クラスを使用し、DES (Data Encryption Standard) アルゴリズムによる暗号化、復号化の例を示しましたが、.NET Framework では、共有キー暗号化アルゴリズムを実装するクラスとして、他にも3つ用意されています(下の表参照)。使い方はDESCryptoServiceProviderと全く同じですので、上記の例の"DESCryptoServiceProvider"を適当なクラス名で置き換えるだけでも動くでしょう。

.NETに用意されている4つの共有キー暗号方式のアルゴリズムについて、表にしてみました。ここで、「デフォルトKeySize」と「デフォルトBlockSize」とは、そのクラスのインスタンスが作成されたときのKeySizeとBlockSizeプロパティの値です。「KeySize」「BlockSize」「LegalKeySizes」「LegalBlockSizes」のそれぞれの値は、私が自分の環境で実際に調べた結果です。

クラス名 アルゴリズム 説明 デフォルト KeySize LegalKeySizes
(MinSize, MaxSize, SkipSize)
デフォルト BlockSize LegalBlockSizes
(MinSize, MaxSize, SkipSize)
DESCryptoServiceProvider DES (Data Encryption Standard) e-Words : DES 64 64, 64, 0 64 64, 64, 0
RC2CryptoServiceProvider RC2(Rivest’s Cipher 2) e-Words : RC2 128 40, 128, 8 64 64, 64, 0
RijndaelManaged Rijndael(ラインダール) e-Words : Rijndael 256 128, 256, 64 128 128, 256, 64
TripleDESCryptoServiceProvider TripleDES(トリプルDES) e-Words : Triple DES 192 128, 192, 64 64 64, 64, 0
  • 履歴:
  • 2004/9/12 ResizeBytesArrayメソッドを修正。
  • 2005/3/26 ResizeBytesArrayメソッドを修正。
  • 2005/6/27 VB.NETのEncryptFileメソッドを修正(掲示板No11322参照)。

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • このサイトで紹介されているコードの多くは、例外処理が省略されています。例外処理については、こちらをご覧ください。