【C#】平文の暗号化と複合を行ってくれるクラスを作成してみた

# 概要
C#で、平文の暗号化と複合を行ってくれるクラスのサンプルコードを作成しました。
暗号化方式はAES256で、それをBase64にエンコードしたものを暗号文として作成しています。

# AESとは
AESは「Advanced Encryption Standard」の略で、アメリカ政府が策定した暗号の規格のことです。DES「Data Encryption Standard」といわれる暗号の規格に代わるものとして、2001年に採用されました。
AESもDESも、どちらも「共通鍵暗号方式」です。

# Base64とは
バイナリデータを一定の決まりに基づいてテキストデータに変換する、変換方式の一つです。
64種類の英数字のみを用いてデータを表現する方式です。

参考：[文字列をBase64でエンコード⇔デコードできるサイトがありました。](
https://www.convertstring.com/ja/EncodeDecode/Base64Encode)

# 暗号化クラス
```C#
    /// <summary>
    /// 暗号機
    /// </summary>
    public class Cryptography
    {
        /// <summary>
        /// 文字列をAES256で暗号化 + Base64にする
        /// </summary>
        /// <param name="plainText">平文</param>
        /// <param name="key">暗号化キー</param>
        /// <param name="iv">初期化ベクトル</param>
        /// <param name="cipherMode">暗号モード</param>
        /// <param name="paddingMode">パディングモード</param>
        /// <returns>暗号化+Base64された文字列</returns>
        public static string AesEncryptToBase64String(string plainText, byte[] key, byte[] iv, CipherMode cipherMode = CipherMode.CBC, PaddingMode paddingMode = PaddingMode.PKCS7)
            => Convert.ToBase64String(AesEncryptToBytes(plainText, key, iv, cipherMode, paddingMode));

        /// <summary>
        /// 秘密鍵を使用して文字列をAES256で暗号化する
        /// </summary>
        /// <param name="plainText">平文</param>
        /// <param name="key">暗号化キー</param>
        /// <param name="iv">初期化ベクトル</param>
        /// <param name="cipherMode">暗号モード</param>
        /// <param name="paddingMode">パディングモード</param>
        /// <returns>暗号化された文字列</returns>
        private static byte[] AesEncryptToBytes(string plainText, byte[] key, byte[] iv, CipherMode cipherMode = CipherMode.CBC, PaddingMode paddingMode = PaddingMode.PKCS7)
        {
            if (plainText == null || plainText.Length <= 0)
            {
                // エラー処理
            }

            if (key == null || key.Length <= 0)
            {
                // エラー処理
            }

            if (iv == null || iv.Length <= 0)
            {
                // エラー処理
            }

            byte[] encrypted;
            using (var rijndael = new RijndaelManaged())
            {
                rijndael.Padding = paddingMode;  // 埋め込みモード
                rijndael.Mode = cipherMode;      // 操作モード
                rijndael.KeySize = 256;          // 共有キーのサイズ (ビット)
                rijndael.BlockSize = 128;        // ブロックサイズ（ビット）

                rijndael.Key = key;              // 秘密キー
                rijndael.IV = iv;                // 初期化ベクトル

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

                using (var ms = new MemoryStream())
                {
                    using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
                    using (var sw = new StreamWriter(cs))
                    {
                        sw.Write(plainText);
                    }
                    encrypted = ms.ToArray();
                }
            }

            return encrypted;
        }

        /// <summary>
        /// AES256で暗号化されたBASE64文字列を復号する
        /// </summary>
        /// <param name="cipherText">暗号文</param>
        /// <param name="key">暗号化キー</param>
        /// <param name="iv">初期化ベクトル</param>
        /// <param name="cipherMode">暗号モード</param>
        /// <param name="paddingMode">パディングモード</param>
        /// <returns>複合した文字列</returns>
        public static string AesDecryptFromBase64String(string cipherText, byte[] key, byte[] iv, CipherMode cipherMode = CipherMode.CBC, PaddingMode paddingMode = PaddingMode.PKCS7)
            => DecryptStringFromBytes(Convert.FromBase64String(cipherText), key, iv, paddingMode, cipherMode);

        /// <summary>
        /// AES256で暗号化されたデータを復号する
        /// </summary>
        /// <param name="cipherText">暗号文</param>
        /// <param name="key">暗号化キー</param>
        /// <param name="iv">初期化ベクトル</param>
        /// <param name="cipherMode">暗号モード</param>
        /// <param name="paddingMode">パディングモード</param>
        /// <returns>複合した文字列</returns>
        private static string DecryptStringFromBytes(byte[] cipherText, byte[] key, byte[] iv, PaddingMode paddingMode, CipherMode cipherMode)
        {
            if (cipherText == null || cipherText.Length <= 0)
            {
                // エラー処理
            }

            if (key == null || key.Length <= 0)
            {
                // エラー処理
            }

            if (iv == null || iv.Length <= 0)
            {
                // エラー処理
            }

            string plaintext = null;
            using (var rijndael = new RijndaelManaged())
            {
                rijndael.Padding = paddingMode;  // 埋め込みモード
                rijndael.Mode = cipherMode;      // 操作モード
                rijndael.KeySize = 256;          // 共有キーのサイズ (ビット)
                rijndael.BlockSize = 128;        // ブロックサイズ（ビット）

                rijndael.Key = key;              // 秘密キー
                rijndael.IV = iv;                // 初期化ベクトル

                var decryptor = rijndael.CreateDecryptor(rijndael.Key, rijndael.IV);
                using (var ms = new MemoryStream(cipherText))
                using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
                using (var sr = new StreamReader(cs))
                {
                    plaintext = sr.ReadToEnd();
                }
                
            }

            return plaintext;
        }
    }
```
`RijndaelManaged`クラスの「Rijndael」は「ラインダール」と読みます。

# 使用方法
### 暗号化
```C#
    string plainText = "暗号化したい文字列";
    var key = new byte[32];
    var iv = new byte[16];
    using (var rng = RandomNumberGenerator.Create())
    {
        rng.GetBytes(key);
        rng.GetBytes(iv);
    }
    
    string cipherText = Cryptography.AesEncryptToBase64String(plainText, key, iv);
```

上記では安全のため、毎回ランダムに暗号化キーと初期化ベクトルを設定しています。
※暗号化キーと初期化ベクトルが毎回同じの場合、同じ平文を暗号化すると、同じ暗号文になってしまうためです。
デフォルト引数として、暗号モードはAESや3DESでよく使用されている「CBC」に、パディングモードは「PKCS7」に設定しています。

### 復号
```C#
    string plainText = Cryptography.AesDecryptFromBase64String(cipherText, key, iv);
```
復号するには、暗号化キーと初期化ベクトルは暗号化で使用したものと同じものを使用する必要があります。

## 注意点
- .NET Coreでは`RijndaelManaged`クラスの使用は非推奨とされておりました。
その代わりとして、`Aes`クラスを使用するべきだといわれています。
<br>

- `RijndaelManaged`クラスのプロパティの設定は、順序依存性がありそう？かなと思われました。:thinking:

暗号化のメソッドの中で、下記の部分を
```C#
    rijndael.Padding = paddingMode;  // 埋め込みモード
    rijndael.Mode = cipherMode;      // 操作モード
    rijndael.KeySize = 256;          // 共有キーのサイズ (ビット)
    rijndael.BlockSize = 128;        // ブロックサイズ（ビット）

    rijndael.Key = key;              // 秘密キー
    rijndael.IV = iv;                // 初期化ベクトル
```
下記のように、先に秘密キーと初期化ベクトルを設定するように変更すると、
```C#
    rijndael.Key = key;              // 秘密キー
    rijndael.IV = iv;                // 初期化ベクトル
    
    rijndael.Padding = paddingMode;  // 埋め込みモード
    rijndael.Mode = cipherMode;      // 操作モード
    rijndael.KeySize = 256;          // 共有キーのサイズ (ビット)
    rijndael.BlockSize = 128;        // ブロックサイズ（ビット）
```
復号の際の`plaintext = sr.ReadToEnd();`の部分で、
「パディングは無効なので削除できません。」というエラーが発生しました。

```C#
    var decryptor = rijndael.CreateDecryptor(rijndael.Key, rijndael.IV);
    using (var ms = new MemoryStream(cipherText))
    using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
    using (var sr = new StreamReader(cs))
    {
        // ここでエラー
        plaintext = sr.ReadToEnd();
    }
```

## おわりに
暗号化も奥が深いので、まだまだ勉強が足りないと感じました。
Aesクラスを使用しての暗号化クラスも、また記載したいと思います。