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

DOBON.NET

パスワードでファイルを暗号化する

注意:NTFSファイルシステムによってファイルを暗号化する(暗号化属性を付加する)方法は、「ファイルやフォルダをNTFS暗号化する」で紹介しています。

ここでは、共通鍵暗号(共有キー暗号、対称鍵暗号、秘密鍵暗号、慣用暗号)方式によって、ファイルを暗号化、復号化する方法を説明します。最終的には、パスワードで暗号化できるようにすることを目標とします。

共通鍵暗号方式でファイルを暗号化、復号化する

まずは、共通鍵暗号方式でファイルを暗号化、復号化する基本的な方法を説明します。

.NET Frameworkでは、共有鍵暗号化アルゴリズムを実装したクラスが幾つか用意されています。これらのクラスは、SymmetricAlgorithmクラスから派生しています。このようなクラスを表にまとめると、以下のようになります。項目「LegalKeySizes」「LegalBlockSizes」は、そのクラスのインスタンスのLegalKeySizesプロパティ(サポートされているキーサイズをビット単位で取得)、および、LegalBlockSizesプロパティ(サポートされているブロックサイズをビット単位で取得)が返す値です。また、これらの項目内の最後の括弧内の数字は、KeySizeプロパティ、および、BlockSizeプロパティのデフォルトの値です。

クラス名 アルゴリズム 説明 LegalKeySizes
(MinSize, MaxSize, SkipSize)
LegalBlockSizes
(MinSize, MaxSize, SkipSize)
DESCryptoServiceProvider DES (Data Encryption Standard) 暗号サービス プロバイダー (CSP: Cryptographic Service Provider) 実装
DESは「電子政府推奨暗号リスト」に無い
64, 64, 0 (64) 64, 64, 0 (64)
RC2CryptoServiceProvider RC2(Rivest's Cipher 2) 暗号サービス プロバイダー (CSP: Cryptographic Service Provider) 実装
RC2は「電子政府推奨暗号リスト」に無い
40, 128, 8 (128) 64, 64, 0 (64)
TripleDESCryptoServiceProvider TripleDES(トリプルDES 暗号サービス プロバイダー (CSP: Cryptographic Service Provider) 実装
電子政府推奨暗号リスト」では当面の使用を認めるとしている
128, 192, 64 (192) 64, 64, 0 (64)
RijndaelManaged Rijndael(ラインダール) マネージ実装
AESは、基本的には、ブロックサイズと反復カウントが固定されたRijndael
128, 256, 64 (256) 128, 256, 64 (128)
AesCryptoServiceProvider AES暗号(Advanced Encryption Standard) CAPI (Cryptographic Application Programming Interfaces) 実装
.NET Framework 3.5以上、Windows XP SP2以上、Windows Server 2003以上でサポート
128, 256, 64 (256) 128, 128, 0 (128)
AesManaged AES暗号(Advanced Encryption Standard) マネージ実装
.NET Framework 3.5以上でサポート
128, 256, 64 (256) 128, 128, 0 (128)

早速ですが、具体例を紹介します。以下のコードは、RijndaelManagedクラスを使って、Rijndaelでファイルを暗号化、復号化するメソッドの例です。暗号化で使用する共有キー(Key)と初期化ベクタ(IV)は、ランダムに作成されたものを使用しています。

[VB.NET]
''' <summary>
''' ファイルを暗号化する
''' </summary>
''' <param name="sourceFile">暗号化するファイルパス</param>
''' <param name="destFile">暗号化されたデータを保存するファイルパス</param>
''' <param name="key">暗号化に使用した共有キー</param>
''' <param name="iv">暗号化に使用した初期化ベクタ</param>
Public Shared Sub EncryptFile(ByVal sourceFile As String, _
                              ByVal destFile As String, _
                              ByRef key As Byte(), _
                              ByRef iv As Byte())
    'RijndaelManagedオブジェクトを作成
    Dim rijndael As New System.Security.Cryptography.RijndaelManaged()

    '設定を変更するときは、変更する
    'rijndael.KeySize = 256
    'rijndael.BlockSize = 128
    'rijndael.FeedbackSize = 128
    'rijndael.Mode = System.Security.Cryptography.CipherMode.CBC
    'rijndael.Padding = System.Security.Cryptography.PaddingMode.PKCS7

    '共有キーと初期化ベクタを作成
    'Key、IVプロパティがnullの時に呼びだすと、自動的に作成される
    '自分で作成するときは、GenerateKey、GenerateIVメソッドを使う
    key = rijndael.Key
    iv = rijndael.IV

    '暗号化されたファイルを書き出すためのFileStream
    Dim outFs As New System.IO.FileStream( _
        destFile, System.IO.FileMode.Create, System.IO.FileAccess.Write)
    '対称暗号化オブジェクトの作成
    Dim encryptor As System.Security.Cryptography.ICryptoTransform = _
        rijndael.CreateEncryptor()
    '暗号化されたデータを書き出すためのCryptoStreamの作成
    Dim cryptStrm As New System.Security.Cryptography.CryptoStream( _
        outFs, encryptor, System.Security.Cryptography.CryptoStreamMode.Write)

    '暗号化されたデータを書き出す
    Dim inFs As New System.IO.FileStream( _
        sourceFile, System.IO.FileMode.Open, System.IO.FileAccess.Read)
    Dim bs As Byte() = New Byte(1023) {}
    Dim readLen As Integer
    While True
        readLen = inFs.Read(bs, 0, bs.Length)
        If readLen = 0 Then
            Exit While
        End If
        cryptStrm.Write(bs, 0, readLen)
    End While

    '閉じる
    inFs.Close()
    cryptStrm.Close()
    encryptor.Dispose()
    outFs.Close()
End Sub

''' <summary>
''' ファイルを復号化する
''' </summary>
''' <param name="sourceFile">復号化するファイルパス</param>
''' <param name="destFile">復号化されたデータを保存するファイルパス</param>
''' <param name="key">暗号化に使用した共有キー</param>
''' <param name="iv">暗号化に使用した初期化ベクタ</param>
Public Shared Sub DecryptFile(ByVal sourceFile As String, _
                              ByVal destFile As String, _
                              ByVal key As Byte(), _
                              ByVal iv As Byte())
    'RijndaelManagedオブジェクトの作成
    Dim rijndael As New System.Security.Cryptography.RijndaelManaged()

    '共有キーと初期化ベクタを設定
    rijndael.Key = key
    rijndael.IV = iv

    '暗号化されたファイルを読み込むためのFileStream
    Dim inFs As New System.IO.FileStream( _
        sourceFile, System.IO.FileMode.Open, System.IO.FileAccess.Read)
    '対称復号化オブジェクトの作成
    Dim decryptor As System.Security.Cryptography.ICryptoTransform = _
        rijndael.CreateDecryptor()
    '暗号化されたデータを読み込むためのCryptoStreamの作成
    Dim cryptStrm As New System.Security.Cryptography.CryptoStream( _
        inFs, decryptor, System.Security.Cryptography.CryptoStreamMode.Read)

    '復号化されたデータを書き出す
    Dim outFs As New System.IO.FileStream( _
        destFile, System.IO.FileMode.Create, System.IO.FileAccess.Write)
    Dim bs As Byte() = New Byte(1023) {}
    Dim readLen As Integer
    While True
        '復号化に失敗すると例外CryptographicExceptionが発生
        readLen = cryptStrm.Read(bs, 0, bs.Length)
        If readLen = 0 Then
            Exit While
        End If
        outFs.Write(bs, 0, readLen)
    End While

    '閉じる
    outFs.Close()
    cryptStrm.Close()
    decryptor.Dispose()
    inFs.Close()
End Sub
[C#]
/// <summary>
/// ファイルを暗号化する
/// </summary>
/// <param name="sourceFile">暗号化するファイルパス</param>
/// <param name="destFile">暗号化されたデータを保存するファイルパス</param>
/// <param name="key">暗号化に使用した共有キー</param>
/// <param name="iv">暗号化に使用した初期化ベクタ</param>
public static void EncryptFile(
    string sourceFile, string destFile, out byte[] key, out byte[] iv)
{
    //RijndaelManagedオブジェクトを作成
    System.Security.Cryptography.RijndaelManaged rijndael =
        new System.Security.Cryptography.RijndaelManaged();

    //設定を変更するときは、変更する
    //rijndael.KeySize = 256;
    //rijndael.BlockSize = 128;
    //rijndael.FeedbackSize = 128;
    //rijndael.Mode = System.Security.Cryptography.CipherMode.CBC;
    //rijndael.Padding = System.Security.Cryptography.PaddingMode.PKCS7;

    //共有キーと初期化ベクタを作成
    //Key、IVプロパティがnullの時に呼びだすと、自動的に作成される
    //自分で作成するときは、GenerateKey、GenerateIVメソッドを使う
    key = rijndael.Key;
    iv = rijndael.IV;

    //暗号化されたファイルを書き出すためのFileStream
    System.IO.FileStream outFs = new System.IO.FileStream(
        destFile, System.IO.FileMode.Create, System.IO.FileAccess.Write);
    //対称暗号化オブジェクトの作成
    System.Security.Cryptography.ICryptoTransform encryptor =
        rijndael.CreateEncryptor();
    //暗号化されたデータを書き出すためのCryptoStreamの作成
    System.Security.Cryptography.CryptoStream cryptStrm =
        new System.Security.Cryptography.CryptoStream(
            outFs, encryptor,
            System.Security.Cryptography.CryptoStreamMode.Write);

    //暗号化されたデータを書き出す
    System.IO.FileStream inFs = new System.IO.FileStream(
        sourceFile, System.IO.FileMode.Open, System.IO.FileAccess.Read);
    byte[] bs = new byte[1024];
    int readLen;
    while ((readLen = inFs.Read(bs, 0, bs.Length)) > 0)
    {
        cryptStrm.Write(bs, 0, readLen);
    }

    //閉じる
    inFs.Close();
    cryptStrm.Close();
    encryptor.Dispose();
    outFs.Close();
}

/// <summary>
/// ファイルを復号化する
/// </summary>
/// <param name="sourceFile">復号化するファイルパス</param>
/// <param name="destFile">復号化されたデータを保存するファイルパス</param>
/// <param name="key">暗号化に使用した共有キー</param>
/// <param name="iv">暗号化に使用した初期化ベクタ</param>
public static void DecryptFile(
    string sourceFile, string destFile, byte[] key, byte[] iv)
{
    //RijndaelManagedオブジェクトの作成
    System.Security.Cryptography.RijndaelManaged rijndael =
        new System.Security.Cryptography.RijndaelManaged();

    //共有キーと初期化ベクタを設定
    rijndael.Key = key;
    rijndael.IV = iv;

    //暗号化されたファイルを読み込むためのFileStream
    System.IO.FileStream inFs = new System.IO.FileStream(
        sourceFile, System.IO.FileMode.Open, System.IO.FileAccess.Read);
    //対称復号化オブジェクトの作成
    System.Security.Cryptography.ICryptoTransform decryptor =
        rijndael.CreateDecryptor();
    //暗号化されたデータを読み込むためのCryptoStreamの作成
    System.Security.Cryptography.CryptoStream cryptStrm =
        new System.Security.Cryptography.CryptoStream(
            inFs, decryptor,
            System.Security.Cryptography.CryptoStreamMode.Read);

    //復号化されたデータを書き出す
    System.IO.FileStream outFs = new System.IO.FileStream(
        destFile, System.IO.FileMode.Create, System.IO.FileAccess.Write);
    byte[] bs = new byte[1024];
    int readLen;
    //復号化に失敗すると例外CryptographicExceptionが発生
    while ((readLen = cryptStrm.Read(bs, 0, bs.Length)) > 0)
    {
        outFs.Write(bs, 0, readLen);
    }

    //閉じる
    outFs.Close();
    cryptStrm.Close();
    decryptor.Dispose();
    inFs.Close();
}

このメソッドの使用例は、以下のようなコードになります。暗号化で使用した共有キーと初期化ベクタを覚えておき、これらを復号化で使用します。これらを忘れてしまうと、復元できません。

[VB.NET]
Dim key As Byte(), iv As Byte()

'ファイルを暗号化する
EncryptFile("C:\test.txt", "C:\test.enc", key, iv)
'暗号化したファイルを復号化する
DecryptFile("C:\test.enc", "C:\test2.txt", key, iv)
[C#]
byte[] key, iv;

//ファイルを暗号化する
EncryptFile(@"C:\test.txt", @"C:\test.enc", out key, out iv);
//暗号化したファイルを復号化する
DecryptFile(@"C:\test.enc", @"C:\test2.txt", key, iv);

パスワードから共有キーを作成する

パスワードを使って暗号化するためには、パスワードから共有キーを作成する必要があります。パスワードから共有キーを作成する方法は、「Generating a Key from a Password - .NET Security Blog」で紹介されています。これによると、その方法は3つあり、PasswordDeriveBytes.CryptDeriveKeyメソッドを使う方法と、PasswordDeriveBytes.GetBytesメソッドを使う方法、それにRfc2898DeriveBytes.GetBytesメソッドを使う方法です。

PasswordDeriveBytes.CryptDeriveKeyメソッドは、MSDNによると、Crypto APIのCryptDeriveKey関数のラッパーであり、Crypto APIを使用するアプリケーションとの相互運用性を確保するために用意されているということです。CryptDeriveKeyメソッドを使用してパスワードから共有キーを作成する例は、MSDNのPasswordDeriveBytesクラスのページにあります。

PasswordDeriveBytes.GetBytesメソッドは、RFC2898で定義されているPBKDF1アルゴリズムを使用します。このメソッドは、.NET Framework 2.0からはObsoleteAttribute属性がマークされており、Rfc2898DeriveBytes.GetBytesメソッドを使用するように警告されます。

Rfc2898DeriveBytes.GetBytesメソッドは、RFC2898で定義されているPBKDF2アルゴリズムを使用します。このメソッドは、.NET Framework 2.0以降で使用できます。

先程のEncryptFileメソッドを書き換えて、パスワードで暗号化する例を示します。この例では、Rfc2898DeriveBytes.GetBytesメソッドを使ってパスワードからKeyとIVを作成しています。

[VB.NET]
''' <summary>
''' パスワードから共有キーと初期化ベクタを生成する
''' </summary>
''' <param name="password">基になるパスワード</param>
''' <param name="keySize">共有キーのサイズ(ビット)</param>
''' <param name="key">作成された共有キー</param>
''' <param name="blockSize">初期化ベクタのサイズ(ビット)</param>
''' <param name="iv">作成された初期化ベクタ</param>
Private Shared Sub GenerateKeyFromPassword(ByVal password As String, _
                                           ByVal keySize As Integer, _
                                           ByRef key As Byte(), _
                                           ByVal blockSize As Integer, _
                                           ByRef iv As Byte())
    'パスワードから共有キーと初期化ベクタを作成する
    'saltを決める
    Dim salt As Byte() = System.Text.Encoding.UTF8.GetBytes("saltは必ず8バイト以上")
    'Rfc2898DeriveBytesオブジェクトを作成する
    Dim deriveBytes As New System.Security.Cryptography.Rfc2898DeriveBytes( _
        password, salt)
    '.NET Framework 1.1以下の時は、PasswordDeriveBytesを使用する
    'Dim deriveBytes As New System.Security.Cryptography.PasswordDeriveBytes( _
    '    password, salt)
    '反復処理回数を指定する デフォルトで1000回
    deriveBytes.IterationCount = 1000

    '共有キーと初期化ベクタを生成する
    key = deriveBytes.GetBytes(keySize \ 8)
    iv = deriveBytes.GetBytes(blockSize \ 8)
End Sub

''' <summary>
''' ファイルを暗号化する
''' </summary>
''' <param name="sourceFile">暗号化するファイルパス</param>
''' <param name="destFile">暗号化されたデータを保存するファイルパス</param>
''' <param name="password">暗号化に使用するパスワード</param>
Public Shared Sub EncryptFile(ByVal sourceFile As String, _
                              ByVal destFile As String, _
                              ByVal password As String)
    Dim rijndael As New System.Security.Cryptography.RijndaelManaged()

    'パスワードから共有キーと初期化ベクタを作成
    Dim key As Byte(), iv As Byte()
    GenerateKeyFromPassword(password, rijndael.KeySize, key, rijndael.BlockSize, iv)
    rijndael.Key = key
    rijndael.IV = iv

    '以下、前のコードと同じ
    Dim outFs As New System.IO.FileStream( _
        destFile, System.IO.FileMode.Create, System.IO.FileAccess.Write)
    Dim encryptor As System.Security.Cryptography.ICryptoTransform = _
        rijndael.CreateEncryptor()
    Dim cryptStrm As New System.Security.Cryptography.CryptoStream( _
        outFs, encryptor, System.Security.Cryptography.CryptoStreamMode.Write)

    Dim inFs As New System.IO.FileStream( _
        sourceFile, System.IO.FileMode.Open, System.IO.FileAccess.Read)
    Dim bs As Byte() = New Byte(1023) {}
    Dim readLen As Integer
    While True
        readLen = inFs.Read(bs, 0, bs.Length)
        If readLen = 0 Then
            Exit While
        End If
        cryptStrm.Write(bs, 0, readLen)
    End While

    inFs.Close()
    cryptStrm.Close()
    encryptor.Dispose()
    outFs.Close()
End Sub

''' <summary>
''' ファイルを復号化する
''' </summary>
''' <param name="sourceFile">復号化するファイルパス</param>
''' <param name="destFile">復号化されたデータを保存するファイルパス</param>
''' <param name="password">暗号化に使用したパスワード</param>
Public Shared Sub DecryptFile(ByVal sourceFile As String, _
                              ByVal destFile As String, _
                              ByVal password As String)
    Dim rijndael As New System.Security.Cryptography.RijndaelManaged()

    'パスワードから共有キーと初期化ベクタを作成
    Dim key As Byte(), iv As Byte()
    GenerateKeyFromPassword(password, rijndael.KeySize, key, rijndael.BlockSize, iv)
    rijndael.Key = key
    rijndael.IV = iv

    '以下、前のコードと同じ
    Dim inFs As New System.IO.FileStream( _
        sourceFile, System.IO.FileMode.Open, System.IO.FileAccess.Read)
    Dim decryptor As System.Security.Cryptography.ICryptoTransform = _
        rijndael.CreateDecryptor()
    Dim cryptStrm As New System.Security.Cryptography.CryptoStream( _
        inFs, decryptor, System.Security.Cryptography.CryptoStreamMode.Read)

    Dim outFs As New System.IO.FileStream( _
        destFile, System.IO.FileMode.Create, System.IO.FileAccess.Write)
    Dim bs As Byte() = New Byte(1023) {}
    Dim readLen As Integer
    While True
        readLen = cryptStrm.Read(bs, 0, bs.Length)
        If readLen = 0 Then
            Exit While
        End If
        outFs.Write(bs, 0, readLen)
    End While

    outFs.Close()
    cryptStrm.Close()
    decryptor.Dispose()
    inFs.Close()
End Sub
[C#]
/// <summary>
/// パスワードから共有キーと初期化ベクタを生成する
/// </summary>
/// <param name="password">基になるパスワード</param>
/// <param name="keySize">共有キーのサイズ(ビット)</param>
/// <param name="key">作成された共有キー</param>
/// <param name="blockSize">初期化ベクタのサイズ(ビット)</param>
/// <param name="iv">作成された初期化ベクタ</param>
private static void GenerateKeyFromPassword(string password,
    int keySize, out byte[] key, int blockSize, out byte[] iv)
{
    //パスワードから共有キーと初期化ベクタを作成する
    //saltを決める
    byte[] salt = System.Text.Encoding.UTF8.GetBytes("saltは必ず8バイト以上");
    //Rfc2898DeriveBytesオブジェクトを作成する
    System.Security.Cryptography.Rfc2898DeriveBytes deriveBytes =
        new System.Security.Cryptography.Rfc2898DeriveBytes(password, salt);
    //.NET Framework 1.1以下の時は、PasswordDeriveBytesを使用する
    //System.Security.Cryptography.PasswordDeriveBytes deriveBytes =
    //    new System.Security.Cryptography.PasswordDeriveBytes(password, salt);
    //反復処理回数を指定する デフォルトで1000回
    deriveBytes.IterationCount = 1000;

    //共有キーと初期化ベクタを生成する
    key = deriveBytes.GetBytes(keySize / 8);
    iv = deriveBytes.GetBytes(blockSize / 8);
}

/// <summary>
/// ファイルを暗号化する
/// </summary>
/// <param name="sourceFile">暗号化するファイルパス</param>
/// <param name="destFile">暗号化されたデータを保存するファイルパス</param>
/// <param name="password">暗号化に使用するパスワード</param>
public static void EncryptFile(
    string sourceFile, string destFile, string password)
{
    System.Security.Cryptography.RijndaelManaged rijndael =
        new System.Security.Cryptography.RijndaelManaged();

    //パスワードから共有キーと初期化ベクタを作成
    byte[] key, iv;
    GenerateKeyFromPassword(
        password, rijndael.KeySize, out key, rijndael.BlockSize, out iv);
    rijndael.Key = key;
    rijndael.IV = iv;

    //以下、前のコードと同じ
    System.IO.FileStream outFs = new System.IO.FileStream(
        destFile, System.IO.FileMode.Create, System.IO.FileAccess.Write);
    System.Security.Cryptography.ICryptoTransform encryptor =
        rijndael.CreateEncryptor();
    System.Security.Cryptography.CryptoStream cryptStrm =
        new System.Security.Cryptography.CryptoStream(
            outFs, encryptor,
            System.Security.Cryptography.CryptoStreamMode.Write);

    System.IO.FileStream inFs = new System.IO.FileStream(
        sourceFile, System.IO.FileMode.Open, System.IO.FileAccess.Read);
    byte[] bs = new byte[1024];
    int readLen;
    while ((readLen = inFs.Read(bs, 0, bs.Length)) > 0)
    {
        cryptStrm.Write(bs, 0, readLen);
    }

    inFs.Close();
    cryptStrm.Close();
    encryptor.Dispose();
    outFs.Close();
}

/// <summary>
/// ファイルを復号化する
/// </summary>
/// <param name="sourceFile">復号化するファイルパス</param>
/// <param name="destFile">復号化されたデータを保存するファイルパス</param>
/// <param name="password">暗号化に使用したパスワード</param>
public static void DecryptFile(
    string sourceFile, string destFile, string password)
{
    System.Security.Cryptography.RijndaelManaged rijndael =
        new System.Security.Cryptography.RijndaelManaged();

    //パスワードから共有キーと初期化ベクタを作成
    byte[] key, iv;
    GenerateKeyFromPassword(
        password, rijndael.KeySize, out key, rijndael.BlockSize, out iv);
    rijndael.Key = key;
    rijndael.IV = iv;

    //以下、前のコードと同じ
    System.IO.FileStream inFs = new System.IO.FileStream(
        sourceFile, System.IO.FileMode.Open, System.IO.FileAccess.Read);
    System.Security.Cryptography.ICryptoTransform decryptor =
        rijndael.CreateDecryptor();
    System.Security.Cryptography.CryptoStream cryptStrm =
        new System.Security.Cryptography.CryptoStream(
            inFs, decryptor,
            System.Security.Cryptography.CryptoStreamMode.Read);

    System.IO.FileStream outFs = new System.IO.FileStream(
        destFile, System.IO.FileMode.Create, System.IO.FileAccess.Write);
    byte[] bs = new byte[1024];
    int readLen;
    while ((readLen = cryptStrm.Read(bs, 0, bs.Length)) > 0)
    {
        outFs.Write(bs, 0, readLen);
    }

    outFs.Close();
    cryptStrm.Close();
    decryptor.Dispose();
    inFs.Close();
}

この例では、Rfc2898DeriveBytesのコンストラクタに渡すsaltは固定されたデータです。もしsaltをランダムに作成するのであれば、saltの代わりに、saltのサイズをバイト単位の整数でRfc2898DeriveBytesのコンストラクタに渡すことができます。saltの値は、Rfc2898DeriveBytesのSaltプロパティで取得できます。

  • 履歴:
  • 2004/9/12 ResizeBytesArrayメソッドを修正。
  • 2005/3/26 ResizeBytesArrayメソッドを修正。
  • 2005/6/27 VB.NETのEncryptFileメソッドを修正(掲示板No11322参照)。
  • 2010/10/3 「共通鍵暗号方式でファイルを暗号化、復号化する」と「パスワードから共有キーを作成する」に分ける形にして、内容を大幅に書き換える。

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

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