c#の暗号化クラスを使ってみた（AES,RSA）

#はじめに

C#のSystem.Security.Cryptography 名前空間ではハッシュや乱数生成、メッセージ認証といった操作や、データの暗号化アルゴリズムが提供されています。
今回はその暗号化アルゴリズムのうち、対称鍵暗号（共通鍵暗号）の1つであるAES暗号と、公開鍵暗号の1つであるRSA暗号を用いて平文を暗号化し暗号文を取得、また逆に暗号文を復号し平文を求めるプログラムを書いてみました。

#AES暗号とは？

AES(Advanced Encryption Standard)はRijndaelとも呼ばれ
旧規格の対称鍵暗号であるDES(Data Encryption Standard)の安全性が低下したために、NIST(アメリカ国立標準技術研空所)が公募し、2000年に選定された対称暗号である。

#AES暗号の実装

それでは実際にコードを書いてAES暗号を利用してみます。
AES暗号は**System.Security.Cryptography.RijndaelManagedクラス**で提供されています。
RijndaelManaged クラス : https://msdn.microsoft.com/ja-jp/library/system.security.cryptography.rijndaelmanaged(v=vs.110).aspx
今回はこれを利用します。

以下は実際に作成したコードです。

```AESCryption.cs
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

public static class AESCryption
{
    private const string AES_IV  = @"pf69DL6GrWFyZcMK";
    private const string AES_Key = @"9Fix4L4HB4PKeKWY";

    public static void Main()
    {
        // 平文の文字列
        string plainText = "Hello, World!";

        Console.WriteLine("PlainText : {0}\n", plainText);

        // 暗号化された文字列
        string cipher = Encrypt(plainText, AES_IV, AES_Key);
        Console.WriteLine("Cipher : {0}\n" ,cipher);

        Console.WriteLine("Decrypted : {0}\n", Decrypt(cipher, AES_IV, AES_Key));
    }

    /// <summary>
    /// 対称鍵暗号を使って文字列を暗号化する
    /// </summary>
    /// <param name="text">暗号化する文字列</param>
    /// <param name="iv">対称アルゴリズムの初期ベクター</param>
    /// <param name="key">対称アルゴリズムの共有鍵</param>
    /// <returns>暗号化された文字列</returns>
    public static string Encrypt(string text, string iv, string key)
    {

        using (RijndaelManaged rijndael = new RijndaelManaged())
        {
            rijndael.BlockSize = 128;
            rijndael.KeySize = 128;
            rijndael.Mode = CipherMode.CBC;
            rijndael.Padding = PaddingMode.PKCS7;

            rijndael.IV = Encoding.UTF8.GetBytes(iv);
            rijndael.Key = Encoding.UTF8.GetBytes(key);

            ICryptoTransform encryptor = rijndael.CreateEncryptor(rijndael.Key, rijndael.IV);

            byte[] encrypted;
            using (MemoryStream mStream = new MemoryStream())
            {
                using (CryptoStream ctStream = new CryptoStream(mStream, encryptor, CryptoStreamMode.Write))
                {
                    using (StreamWriter sw = new StreamWriter(ctStream))
                    {
                        sw.Write(text);
                    }
                    encrypted = mStream.ToArray();
                }
            }
            return (System.Convert.ToBase64String(encrypted));
        }
    }

    /// <summary>
    /// 対称鍵暗号を使って暗号文を復号する
    /// </summary>
    /// <param name="cipher">暗号化された文字列</param>
    /// <param name="iv">対称アルゴリズムの初期ベクター</param>
    /// <param name="key">対称アルゴリズムの共有鍵</param>
    /// <returns>復号された文字列</returns>
    public static string Decrypt(string cipher, string iv, string key)
    {
        using (RijndaelManaged rijndael = new RijndaelManaged())
        {
            rijndael.BlockSize = 128;
            rijndael.KeySize = 128;
            rijndael.Mode = CipherMode.CBC;
            rijndael.Padding = PaddingMode.PKCS7;

            rijndael.IV = Encoding.UTF8.GetBytes(iv);
            rijndael.Key = Encoding.UTF8.GetBytes(key);

            ICryptoTransform decryptor = rijndael.CreateDecryptor(rijndael.Key, rijndael.IV);

            string plain = string.Empty;
            using (MemoryStream mStream = new MemoryStream(System.Convert.FromBase64String(cipher)))
            {
                using (CryptoStream ctStream = new CryptoStream(mStream, decryptor, CryptoStreamMode.Read))
                {
                    using (StreamReader sr = new StreamReader(ctStream))
                    {
                        plain = sr.ReadLine();
                    }
                }
            }
            return plain;
        }
    }
}
```

Encrypt()は平文と対称アルゴリズムの初期ベクトル、共有鍵の文字列を引数に取り、暗号文の文字列を返す関数である。
一方Decrypt()はその逆で、暗号文と初期ベクトル、共有鍵から平文の文字列を返す関数です。

平文は"Hello, World!"という文字列で、初期ベクトル、共通鍵はBlockSizeとKeySizeを共に128ビットに指定しているので、適当な16バイト文字列を指定しています。

実行結果は以下の通り

```
PlainText : Hello, World!

Cipher : p8ITppfvm6QnVtL/Ji9/ZQ==

Decrypted : Hello, World!
```
このように、平文を暗号化した文字列を復号して元の平文が得られていることが確認できました。


#RSA暗号とは？
続いてはRSA暗号です。
RSA暗号は公開鍵暗号の1つで、公開鍵と秘密鍵の2つの鍵を用いて暗号化と復号を行う方法であり、暗号やデジタル署名を実現できる方式です。

#RSA暗号の実装
それではこちらも実装してみます。
RSA暗号の実装には
RSACryptoServiceProvider クラス：https://msdn.microsoft.com/ja-jp/library/system.security.cryptography.rsacryptoserviceprovider(v=vs.110).aspx
を利用します。

以下は作成したソースコードです。

```RSACryption.cs
using System;
using System.Security.Cryptography;
using System.Text;

class RSAEncryption
{
    static void Main(string[] args)
    {
        // 平文の文字列
        string plainText = "Hello, world!";
        // 暗号化、復号された文字列
        string encrypted, decrypted;
        // 公開鍵と秘密鍵
        string publicKey, privateKey;

        using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
        {
            // 公開鍵、秘密鍵をXML形式で取得する
            publicKey = rsa.ToXmlString(false);
            privateKey = rsa.ToXmlString(true);

            Console.WriteLine("PlainText\n{0}\n", plainText);

            encrypted = Encrypt(plainText, publicKey);

            Console.WriteLine("Encrypted\n{0}\n",encrypted);

            decrypted = Decrypt(encrypted, privateKey);

            Console.WriteLine("Decrypted\n{0}\n",decrypted);
        }
    }

    /// <summary>
    /// 公開鍵暗号で文字列を暗号化する
    /// </summary>
    /// <param name="text">平文の文字列</param>
    /// <param name="publickey">公開鍵</param>
    /// <returns>暗号化された文字列</returns>
    public static string Encrypt(string text,string publickey)
    {
        using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
        {
            rsa.FromXmlString(publickey);

            byte[] data = Encoding.UTF8.GetBytes(text);

            data = rsa.Encrypt(data, false);

            return Convert.ToBase64String(data);
        }
    }

    /// <summary>
    /// 対称鍵暗号で暗号文を復号する
    /// </summary>
    /// <param name="cipher">平文の文字列</param>
    /// <param name="privatekey">秘密鍵</param>
    /// <returns>復号された文字列</returns>
    public static string Decrypt(string cipher, string privatekey)
    {
        using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
        {
            rsa.FromXmlString(privatekey);

            byte[] data = Convert.FromBase64String(cipher);

            data = rsa.Decrypt(data, false);

            return Encoding.UTF8.GetString(data);
        }
    }
}
```

Encrypt()は平文の文字列を公開鍵で暗号化する関数で
Decrypt()は暗号文の文字列を秘密鍵で暗号化する関数です。
公開鍵、秘密鍵の作成はRSAクラスのメソッドToXmlString()を利用します。
このメソッドは作成したRSAオブジェクトのキーを格納するXML文字列を返します。
また、引数をtrueとすることで公開、秘密のRSAキーが含まれ、falseをとすると秘密キーだけが含まれます。

暗号化、複合はそれぞれRSACryptoServiceProviderクラスの
Encrypt,Decryptメソッドを利用します。
これらのメソッドは第二引数をfalseとすることでPKCS#1 v1.5パディングを使用することができます。
（trueとすると直接RSAを復号する)

RSA暗号方式は

- 平文が小さいと、暗号文から平文が容易に計算できてしまう、
- 暗号文を分解して、個々の暗号文に対応する平文を入手できる（選択暗号文攻撃）と、元の暗号文に対応する平文を求めることができてしまう、
- 攻撃者が暗号文を変形して、平文自体を知ることはできないが、平文を変形できてしまう（非展性がない）

といった脆弱性があるため、暗号化前に適切なパディングを行っている。 
[RSA暗号 - Wikipedia](https://ja.wikipedia.org/wiki/RSA%E6%9A%97%E5%8F%B7) より引用


実行した結果以下の通り

```
PlainText
Hello, world!

Encrypted
eMQaYSvPhr+Yy6NrhQtFKeylzqf+HjkmgL+/z1ANujBB0GouOjH3FC9SYc8SN4MwIuBHZpRo2DRpk9SA3CHR2S5upeEPPJrQIfXZIxAwS37+ItvCg8V1T/LhfNAZ15hMR/+ByaJIfBk+jyijBmIDfcm2xmEsTHU8xDAareOhtz0=

Decrypted
Hello, world!
```

こちらも"Hello, world!"といった文字列を平文として指定し、暗号化、復号を行った結果、
平文を暗号化した文字列を復号することで元の平文が得られることが確認できました。