DOBON.NET

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

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

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

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

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

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

クラス名アルゴリズム説明LegalKeySizes
(MinSize, MaxSize, SkipSize)
LegalBlockSizes
(MinSize, MaxSize, SkipSize)
DESCryptoServiceProviderDES (Data Encryption Standard)暗号サービス プロバイダー (CSP: Cryptographic Service Provider) 実装
DESは「電子政府推奨暗号リスト」に無い
64, 64, 0 (64)64, 64, 0 (64)
RC2CryptoServiceProviderRC2(Rivest's Cipher 2)暗号サービス プロバイダー (CSP: Cryptographic Service Provider) 実装
RC2は「電子政府推奨暗号リスト」に無い
40, 128, 8 (128)64, 64, 0 (64)
TripleDESCryptoServiceProviderTripleDES(トリプルDES暗号サービス プロバイダー (CSP: Cryptographic Service Provider) 実装
電子政府推奨暗号リスト」では当面の使用を認めるとしている
128, 192, 64 (192)64, 64, 0 (64)
RijndaelManagedRijndael(ラインダール)マネージ実装
AESは、基本的には、ブロックサイズと反復カウントが固定されたRijndael
128, 256, 64 (256)128, 256, 64 (128)
AesCryptoServiceProviderAES暗号(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)
AesManagedAES暗号(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 「共通鍵暗号方式でファイルを暗号化、復号化する」と「パスワードから共有キーを作成する」に分ける形にして、内容を大幅に書き換える。

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

  • このサイトで紹介されているコードの多くは、例外処理が省略されています。例外処理については、こちらをご覧ください。
  • Windows Vista以降でUACが有効になっていると、ファイルへの書き込みに失敗する可能性があります。詳しくは、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。
共有する

この記事への評価

この記事へのコメント

この記事に関するコメントを投稿するには、下のボタンをクリックしてください。投稿フォームへ移動します。通常のご質問、ご意見等は掲示板へご投稿ください。