AES を使って暗号化・復号化をする

AES は共通鍵のアルゴリズムで、他の共通鍵の暗号化方式と比べて強力なようです。
今回は、AESS の暗号化・複号化のライブラリの使い方を理解してみたいと思います。

# 暗号化と復号化のロジック

基本的にとても簡単です。 [AesManaged](https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.aesmanaged?view=net-5.0) というクラスか、[AesCryptoServiceProvider](https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.aescryptoserviceprovider?view=net-5.0) というクラスが存在するので、そのクラスを使って、暗号化、復号化を行います。このライブラリを使う上で重要なコンセプトは、暗号化の Key と、IV (Initialization Vector)というパラメータです。この二つを理解する必要があります。

## Key

`Key` は暗号化を行うときの共通鍵です。このライブラリのキーサイズを見てみると、256 bit です。`AesManaged` クラスは、初期化した際に、`Key` と `IV` は自動作成されるようです。個別に `GenerateKey()` というメソッドも用意されています。その中のコードを見てみましょう。想像のとり、ランダムな数字のジェネレーターでバイト配列を作成しています。

```csharp
public sealed override void GenerateKey()
{
    byte[] key = new byte[KeySize / BitsPerByte];
    RandomNumberGenerator.Fill(key);
    Key = key;
}
```
最終的に internal sealed partial class RandmNumberGeneratorImplementation で実装されているので、外からはうかがい知ることができません。これと似たような実装を外部ですると、次のようになるでしょう。 [RNGCryptoServiceProvider](https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.rngcryptoserviceprovider?view=net-5.0)はまさにランダムの数字を生成してくれますので、とても良い感じです。`GetBytes` メソッドで、バイト配列を埋めるところも、結構実装が似ていますね。ちなみに、実装を追っていくと、結局 `RandomNumberGeneratorImplementation` にたどり着いたので、多分実装は同じですね。私のマシンでは、`new byte[16]` でないと、256 bit になりませんでしたので、こうしています。

```csharp
        private static string GenerateKey()
        {
            var buffer = new byte[32]; 
            using (var rng = new RNGCryptoServiceProvider())
            {
                rng.GetBytes(buffer);
                return BitConverter.ToString(buffer).Replace("-", string.Empty);
            }
        }
```


## IV
[IV](https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.symmetricalgorithm.iv?view=net-5.0#System_Security_Cryptography_SymmetricAlgorithm_IV) は (Initialization vector) の略です。リンクをした公式ドキュメントには記述してありますが、ASEのアルゴリズムの初期のトランスフォーメーションの初期値として与えられます。ASEのアルゴリズムで、変換を行っていく過程で、前に変換をした値を使うのですが、１回目の時にはその値がありません。ですので、作成して与える必要があります。これが、IVの概要です。細かいことはよく知りませんが、[この記事](https://qiita.com/kkent030315/items/ab0792aa1e8948b57490)を理解すると、わかるようになると思います。

# 実装例

ほとんど公式にあるサンプルそのままですが、出力がどんな感じになるのか試しています。ライブラリが勝手に暗号化・復号化してくれうるので楽ですね。

```csharp
        static void Main(string[] args)
        {
            // AesCryptoServiceProivder を使うときは  CryptoConfig を使うと良い。
            // CryptoConfig cryptoConfig = new CryptoConfig();
            // var aes = (AesCryptoServiceProvider)CryptoConfig.CreateFromName("AES");

            string original = "Here is some data encrypt!";
            using (AesManaged myAes = new AesManaged())
            {
                Console.WriteLine($"KeySize: {myAes.KeySize}");
                Console.WriteLine($"KEY (original) {generatedKey}");
                Console.WriteLine($"KEY: {Convert.ToBase64String(myAes.Key)}");
                Console.WriteLine($"IV: {Convert.ToBase64String(myAes.IV)}");
                byte[] encrypted = EncryptStringToBytes_Aes(original, myAes.Key, myAes.IV);
                string roundtrip = DecryptStringFromBytes_Aes(encrypted, myAes.Key, myAes.IV);
                Console.WriteLine($"Original: {original}");
                Console.WriteLine($"Encrypted: {Encoding.ASCII.GetString(encrypted)}");
                Console.Write($"Encrypted (bytes) :");
                foreach (var i in encrypted)
                {
                    Console.Write($"{ConvertToHex(i)}:");
                }
                Console.WriteLine();
                Console.WriteLine($"Encrypted (base64): {Convert.ToBase64String(encrypted)}");
                Console.WriteLine($"Round Trip: {roundtrip}");
            }
        }

        static byte[] EncryptStringToBytes_Aes(string plainText, byte[] Key, byte[] IV)
        {
            byte[] encrypted;
            using (AesManaged aesAlg = new AesManaged())
            {
                aesAlg.Key = Key;
                aesAlg.IV = IV;

                ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
                using (MemoryStream msEncrypt = new MemoryStream())
                {
                    using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                    {
                        using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                        {
                            swEncrypt.Write(plainText);
                        }

                        encrypted = msEncrypt.ToArray();
                    }
                }
            }

            return encrypted;
        }

        static string DecryptStringFromBytes_Aes(byte[] cipherText, byte[] Key, byte[] IV)
        {
            string plaintext = null;
            using (AesManaged aesAlg = new AesManaged())
            {
                aesAlg.Key = Key;
                aesAlg.IV = IV;
                ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
                using (MemoryStream msDecrypt = new MemoryStream(cipherText))
                {
                    using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                    {
                        using (StreamReader srDecrypt = new StreamReader(csDecrypt))
                        {
                            plaintext = srDecrypt.ReadToEnd();
                        }
                    }
                }
            }

            return plaintext;
        }
    }
```

# すべてのコード

自分がいろいろ試したコードを含めています。キーを自分でジェネレートした例、ライブラリを使ってジェネレートした例

```csharp
       static void Main(string[] args)
        {
            // CryptoConfig cryptoConfig = new CryptoConfig();
            // var aes = (AesCryptoServiceProvider)CryptoConfig.CreateFromName("AES");
            var generatedKey = GenerateKey();
            Console.WriteLine($"Key Length: {generatedKey.Length}");
            string original = "Here is some data encrypt!";
            using (AesManaged myAes = new AesManaged())
            {
                //         public sealed override void GenerateKey()
                //{
                //    byte[] key = new byte[KeySize / BitsPerByte];
                //    RandomNumberGenerator.Fill(key);
                //    Key = key;
                //}
                myAes.GenerateKey();
                Console.WriteLine($"KeySize: {myAes.KeySize}");

                myAes.Key = GetKeyBytes(generatedKey);
                Console.WriteLine($"KEY (original) {generatedKey}");
                Console.WriteLine($"KEY: {Convert.ToBase64String(myAes.Key)}");
                Console.WriteLine($"IV: {Convert.ToBase64String(myAes.IV)}");
                byte[] encrypted = EncryptStringToBytes_Aes(original, myAes.Key, myAes.IV);
                string roundtrip = DecryptStringFromBytes_Aes(encrypted, myAes.Key, myAes.IV);
                Console.WriteLine($"Original: {original}");
                Console.WriteLine($"Encrypted: {Encoding.ASCII.GetString(encrypted)}");
                Console.Write($"Encrypted (bytes) :");
                foreach (var i in encrypted)
                {
                    Console.Write($"{ConvertToHex(i)}:");
                }
                Console.WriteLine();
                Console.WriteLine($"Encrypted (base64): {Convert.ToBase64String(encrypted)}");
                Console.WriteLine($"Round Trip: {roundtrip}");
            }
        }

        // Make the key as 256-bit with the HEX string format
        private static string GenerateKey()
        {
            var buffer = new byte[32]; 
            using (var rng = new RNGCryptoServiceProvider())
            {
                rng.GetBytes(buffer);
                return BitConverter.ToString(buffer).Replace("-", string.Empty);
            }
        }

        public static byte[] GetKeyBytes(string hexOrBase64)
        {
            // only support 32 bytes (256 bits) key length
            if (hexOrBase64.Length == 64)
            {
                return Enumerable.Range(0, hexOrBase64.Length)
                    .Where(x => x % 2 == 0)
                    .Select(x => Convert.ToByte(hexOrBase64.Substring(x, 2), 16))
                    .ToArray();
            }

            return Convert.FromBase64String(hexOrBase64);
        }

        static byte[] EncryptStringToBytes_Aes(string plainText, byte[] Key, byte[] IV)
        {
            byte[] encrypted;
            using (AesManaged aesAlg = new AesManaged())
            {
                aesAlg.Key = Key;
                aesAlg.IV = IV;

                ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
                using (MemoryStream msEncrypt = new MemoryStream())
                {
                    using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                    {
                        using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                        {
                            swEncrypt.Write(plainText);
                        }

                        encrypted = msEncrypt.ToArray();
                    }
                }
            }

            return encrypted;
        }

        static string DecryptStringFromBytes_Aes(byte[] cipherText, byte[] Key, byte[] IV)
        {
            string plaintext = null;
            using (AesManaged aesAlg = new AesManaged())
            {
                aesAlg.Key = Key;
                aesAlg.IV = IV;
                ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
                using (MemoryStream msDecrypt = new MemoryStream(cipherText))
                {
                    using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                    {
                        using (StreamReader srDecrypt = new StreamReader(csDecrypt))
                        {
                            plaintext = srDecrypt.ReadToEnd();
                        }
                    }
                }
            }

            return plaintext;
        }
    }
```

## 実行結果

```
Key Length: 32
KeySize: 256
KEY (original) 8051E074A3DBAB6A6134F2E267C1FBF3
KEY: 8051E074A3DBAB6A6134F2E267C1FBF3
IV: 4egGQBFkFmiLBhfMvkrtxQ==
Original: Here is some data encrypt!
Encrypted: ?↓?ah→?◄6E??X?K.7*.Wqo???☺?
Encrypted (bytes) :A0:1B:9C:19:A4:94:08:07:61:68:1A:EB:11:36:45:B5:B0:58:94:4B:2E:37:2A:2E:57:71:6F:92:D2:F7:01:88:
Encrypted (base64): oBucGaSUCAdhaBrrETZFtbBYlEsuNyouV3FvktL3AYg=
Round Trip: Here is some data encrypt!
```

# Resource
概要はこちらがわかりやすいです。
https://it-trend.jp/encryption/article/64-0070