C#の”AesManaged”でハマった話

AesManagedクラスとは?

AES128,AES256の暗号化を行うことが出来るクラスです。AESとは主に無線通信等で使用される暗号化方式で、現在の技術・リソースでは簡単に破ることが出来ない方式となります(一部の旧型方式を除く)。C#ではその暗号化を簡単に実装できるクラスが用意されています。

Microsoft Docsの例を参考にしてハマった

C#コードを書く際にクラスの使い方を確認するためにMicrosoft Docsにある内容を参考にします。AesManagedクラスについても同様にこちらのページを参考にしていましたが、例に載っているコードでは平文→暗号化→復号化で元の状態に戻せず苦戦しました。下記が問題のコードです。

        static byte[] EncryptStringToBytes_Aes(string plainText, byte[] Key, byte[] IV)
        {
            // Check arguments.
            if (plainText == null || plainText.Length <= 0)
                throw new ArgumentNullException("plainText");
            if (Key == null || Key.Length <= 0)
                throw new ArgumentNullException("Key");
            if (IV == null || IV.Length <= 0)
                throw new ArgumentNullException("IV");
            byte[] encrypted;

            // Create an AesManaged object
            // with the specified key and IV.
            using (AesManaged aesAlg = new AesManaged())
            {
                aesAlg.Key = Key;
                aesAlg.IV = IV;

                // Create an encryptor to perform the stream transform.
                ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);

                // Create the streams used for encryption.
                using (MemoryStream msEncrypt = new MemoryStream())
                {
                    using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                    {
                        using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                        {
                            //Write all data to the stream.
                            swEncrypt.Write(plainText);
                        }
                        encrypted = msEncrypt.ToArray();
                    }
                }
            }

            // Return the encrypted bytes from the memory stream.
            return encrypted;
        }

検証し使用していたコードは上記の例と全く同じではありませんが、IcryptoTransformインスタンス生成時のコンストラクター呼出しで、初期ベクトル宣言にaesAlg.IVを使用しているのがよくないようです。

using (AesManaged aesAlg = new AesManaged())
{
    aesAlg.Key = Key;
    aesAlg.IV = IV;

    // Create an encryptor to perform the stream transform.
    ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);

 

上記のコードを下記内容と同等のものに修正すると、問題なく動作を確認出来ました。

using (AesManaged aesAlg = new AesManaged())
{
    aesAlg.Key = Key;
    aesAlg.IV = IV;

    // Create an encryptor to perform the stream transform.
    ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, IV);

 

原因の推測

検証時には結果が分かりやすいように初期ベクトルIVを固定値としていました。通常AES暗号化は初期ベクトルと鍵が同じであれば出力結果は同じとなるにも関わらず、暗号化を実行する度に結果に変化がありました。ここからは推測ですが、Streamを読み込んで暗号化を行う途中でAesManagedクラス内のIVプロパティが変化しているような気がします。

実使用に合わせて作成したコード

最終的に実使用に合わせて作成したAES128変換のコードを書いておきます。初期ベクトルは自動生成として暗号化後のバイト配列[0]~[15]に埋め込まれます。復号化は反対に初期ベクトル情報を暗号化文から抽出して処理を行います。鍵についてはサイズが16byteに満たない場合は繰り返しにより補完されます。さらにセキュリティを高めるためには繰り返しによる補完ではなく、入力データからハッシュ値を生成し、鍵に使用する方が良いでしょう。途中のif分による警告等は基本的には不要ですが動作確認していた名残りでバグ対策として残しています。

        //binデータのAES128(CBC)暗号化
        public void ByteToAes128CbcEnc(byte[] aesKey)
        {
            //Key Length adjustment
            aesKey = ByteLengthAdjustment(aesKey, 16);

            if (aesKey.Length == 16)
            {

                byte[] encrypted;
                byte[] aesIV;
                byte[] buf = new byte[4096];

                // Create an AesManaged object
                // with the specified key and IV.
                using (AesManaged aesAlg = new AesManaged())
                {
                    aesAlg.GenerateIV();
                    aesIV = aesAlg.IV;
                    aesAlg.Key = aesKey;
                    aesAlg.KeySize = 128;
                    aesAlg.BlockSize = 128;
                    aesAlg.Padding = PaddingMode.PKCS7;
                    aesAlg.Mode = CipherMode.CBC;

                    // Create an encryptor to perform the stream transform.
                    ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesKey, aesIV);

                    // Create the streams used for encryption.
                    using (MemoryStream msEncrypt = new MemoryStream())
                    {
                        using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                        {

                            using (BinaryWriter bwEncrypt = new BinaryWriter(csEncrypt))
                            {
                                //Write all data to the stream.
                                bwEncrypt.Write(byteData);
                            }

                        }
                        //msEncrypt.Close();
                        encrypted = msEncrypt.ToArray();
                    }

                    //byteData[0-15]:aesIV,byteData[16-end]:EncryptedData
                    byteData = new byte[encrypted.Length + 16];
                    Array.Copy(aesIV, 0, byteData, 0, 16);
                    Array.Copy(encrypted, 0, byteData, 16, encrypted.Length);
                    
                }                    
            }
            else
            {
                //メッセージボックスを表示する
                MessageBox.Show("AES IV or AES KEY is invalid", "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }

        }

        
        //strDataのAES128(CBC)暗号化
        public void StringToAes128CbcEnc(string strAesKey)
        {
            byteData = Encoding.UTF8.GetBytes(strData);
            byte[] aesKey = Encoding.UTF8.GetBytes(strAesKey);
            ByteToAes128CbcEnc(aesKey);
            strData = System.Convert.ToBase64String(byteData);
        }
        //binデータのAES128(CBC)復号化
        public void ByteToAes128CbcDec(byte[] aesKey)
        {
            //extract aesIV
            byte[] aesIV = new byte[16];
            byte[] buf = new byte[byteData.Length - 16];
            Array.Copy(byteData, 0, aesIV, 0, 16);
            Array.Copy(byteData, 16, buf, 0, buf.Length);

            //Key Length adjustment
            aesKey = ByteLengthAdjustment(aesKey, 16);

            if (aesIV.Length == 16 && aesKey.Length == 16)
            {
                // Check arguments.
                if (aesKey == null || aesKey.Length <= 0)
                    throw new ArgumentNullException("Key");
                if (aesIV == null || aesIV.Length <= 0)
                    throw new ArgumentNullException("IV");
                //MaxSize
                byte[] decrypted = new byte[Math.Min(maxDecSize,buf.Length)];

                // Create an AesManaged object
                // with the specified key and IV.
                using (AesManaged aesAlg = new AesManaged())
                {
                    aesAlg.Key = aesKey;
                    aesAlg.IV = aesIV;
                    aesAlg.KeySize = 128;
                    aesAlg.BlockSize = 128;
                    aesAlg.Padding = PaddingMode.PKCS7;
                    aesAlg.Mode = CipherMode.CBC;

                    // Create an encryptor to perform the stream transform.
                    ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesKey, aesIV);

                    // Create the streams used for encryption.
                    using (MemoryStream msDecrypt = new MemoryStream(buf))
                    {
                        using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                        {
                            using (BinaryReader srDecrypt = new BinaryReader(csDecrypt))
                            {
                                //Write all data to the stream.
                                //MaxSize
                                decrypted = srDecrypt.ReadBytes(decrypted.Length);
                            }
                        }
                    }
                }
                // Return the encrypted bytes from the memory stream.
                byteData = decrypted;
                decrypted = null;
            }
            else
            {
                //メッセージボックスを表示する
                MessageBox.Show("AES IV or AES KEY is invalid", "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        //strDataのAES128(CBC)復号化
        public void StringToAes128CbcDec(string strAesKey)
        {
            byteData = System.Convert.FromBase64String(strData);
            byte[] aesKey = Encoding.UTF8.GetBytes(strAesKey);
            ByteToAes128CbcDec(aesKey);
            strData = System.Text.Encoding.UTF8.GetString(byteData);
        }
//byte[]長の調整
//長さがleg未満の場合は先頭byteから繰り返し繋げる
//長さがlegを超える場合は超えた分を切り捨てる
public byte[] ByteLengthAdjustment(byte[] data,int leg)
{
    byte[] buf = new byte[leg];
    if (data.Length < leg)
    {
        Array.Copy(data, buf, data.Length);
        int i = 0;
        while (i < leg - data.Length)
        {
            Array.Copy(buf, i, buf, data.Length + i, 1);
            i++;
        }
    }
    else if(data.Length > leg)
    {
        Array.Copy(data, 0, buf, 0, leg);
    }
    return buf;
}

 

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

2 × 5 =