プログラミング素人のはてなブログ

プログラミングも電気回路も専門外の技術屋の末端が勉強したことや作品をアウトプットするブログ。コードに間違いなど見つけられたら、気軽にコメントください。 C#、Python3、ラズパイなど。

C#でLogをとったり暗号化したり

アプリの動作Logをとりたい

アプリのdebugには動作Logを確認することが重要です。
Logとはテキストファイルとして動作させようとしたこと、した結果などが記載されたものです。
テキストファイルなのでStreamWriterで必要に応じて記載してもいいのですが、C#には専用のクラスがあるのでこれを使ってみました。
このクラスを使うと、Debug出力に出したりファイルに出力したりが簡単に切り替えられます。

TL;DR

C#の機能でLog出力を作成
・Log出力はデバッグモニタやファイルに出力が出来る
・暗号化もC#で出来る
・隠しファイルにしたり解除したりも出来る

Log出力の主要部分

dobon.netを参考に以下のように構成しました。

        private void Form1_Load(object sender, EventArgs e)
        {
            InitTrace(); //コメントアウトでLogファイル出力をoff
            timer1.Start();

            //一定日時を過ぎたらlogファイルを削除
            deleteFiles();
        }

      AES AES = new AES();//これを記入して「Alt」+「Enter」でclassファイルを生成する

        private void InitTrace()
        {
            //https://dobon.net/vb/dotnet/programing/tracelisteners.html
            //https://csharp.keicode.com/topics/debug-trace.php

            //TextWriterTraceListenerオブジェクトを作成
            DateTime dt = DateTime.Now;
            string st = dt.ToString("yyyy_MM_dd_HH_mm_ss");
            StreamWriter sw = new StreamWriter(st + ".log");
#if !DEBUG
            System.IO.FileAttributes attr = System.IO.File.GetAttributes(st + ".log");
            System.IO.File.SetAttributes(st + ".log", attr | System.IO.FileAttributes.Hidden);
#endif

            //自動的にフラッシュされるようにする
            sw.AutoFlush = true;
            //スレッドセーフラッパを作成
            TextWriter tw = TextWriter.Synchronized(sw);
            //名前を LogFile としてTextWriterTraceListenerオブジェクトを作成
            TextWriterTraceListener twtl = new TextWriterTraceListener(tw, "LogFile");
            //リスナコレクションに追加する
            Trace.Listeners.Add(twtl);
        }
int count = 0;
        private void timer1_Tick(object sender, EventArgs e)
        {
            count++;
            string text = "Hello_Trace" + count.ToString();

#if !DEBUG
            // 暗号化された文字列
            text = AES.Encrypt(text);

            Trace.WriteLine(text);

            //復号
            text = AES.Decrypt(text);
#endif
            Trace.WriteLine(text);
        }

Form1_LoadInitTrace(); を削除するとLog出力が一切なくなります。
基本はDebug出力に出力されますが、以下のような追加をするとファイルにも出力されます。

//名前を LogFile としてTextWriterTraceListenerオブジェクトを作成
TextWriterTraceListener twtl = new TextWriterTraceListener(tw, "LogFile");
//リスナコレクションに追加する
Trace.Listeners.Add(twtl);

本番環境では暗号化したり隠しファイルにしておいたりが必要な場面があると思います。
しかしDebugではこれはめんどくささになります。

このようなときにReleaseビルドとDebugビルドで動作を切り替える方法がC#にはあって、以下のように書きます。

#if !DEBUG
//リリース時のみ
#endif
#if DEBUG
//デバッグ時のみ
#endif

ここではリリース時に暗号化してファイルに書き込んでいます。

            AES AES = new AES();

#if !DEBUG
            text = AES.Encrypt(text);
            Trace.WriteLine(text);
#endif

暗号化は以下のページをそのまま使ってクラスを作成しました。
yuyu-code.com

using System.IO;
using System.Security.Cryptography;
using System.Text;
namespace WindowsFormsTraceDebug
{
    internal class AES
    {
        // 初期化ベクトル"<半角16文字(1byte=8bit, 8bit*16=128bit>"
        private const string AES_IV_256 = @"pg532LLijWFyZnHB";
        // 暗号化鍵<半角32文字(8bit*32文字=256bit)>
        private const string AES_Key_256 = @"55GB&UfNfUsM(IK<1DSB&YHN7UJM(IK<";

        //https://yuyu-code.com/programming-languages/c-sharp/aes-encryption-decryption/
        /// <summary>
        /// 対称鍵暗号を使って文字列を暗号化する
        /// </summary>
        /// <param name="text">暗号化する文字列</param>
        /// <param name="AES_IV_256">対称アルゴリズムの初期ベクター</param>
        /// <param name="AES_Key_256">対称アルゴリズムの共有鍵</param>
        /// <returns>暗号化された文字列</returns>
        public static string Encrypt(string text)
        {
            using (RijndaelManaged myRijndael = new RijndaelManaged())
            {
                // ブロックサイズ(何文字単位で処理するか)
                myRijndael.BlockSize = 128;
                // 暗号化方式はAES-256を採用
                myRijndael.KeySize = 256;
                // 暗号利用モード
                myRijndael.Mode = CipherMode.CBC;
                // パディング
                myRijndael.Padding = PaddingMode.PKCS7;

                myRijndael.IV = Encoding.UTF8.GetBytes(AES_IV_256);
                myRijndael.Key = Encoding.UTF8.GetBytes(AES_Key_256);

                // 暗号化
                ICryptoTransform encryptor = myRijndael.CreateEncryptor(myRijndael.Key, myRijndael.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();
                    }
                }
                // Base64形式(64種類の英数字で表現)で返す
                return (System.Convert.ToBase64String(encrypted));
            }
        }

        /// <summary>
        /// 対称鍵暗号を使って暗号文を復号する
        /// </summary>
        /// <param name="cipher">暗号化された文字列</param>
        /// <param name="AES_IV_256">対称アルゴリズムの初期ベクター</param>
        /// <param name="AES_Key_256">対称アルゴリズムの共有鍵</param>
        /// <returns>復号された文字列</returns>
        public static string Decrypt(string cipher)
        {
            using (RijndaelManaged rijndael = new RijndaelManaged())
            {
                // ブロックサイズ(何文字単位で処理するか)
                rijndael.BlockSize = 128;
                // 暗号化方式はAES-256を採用
                rijndael.KeySize = 256;
                // 暗号利用モード
                rijndael.Mode = CipherMode.CBC;
                // パディング
                rijndael.Padding = PaddingMode.PKCS7;

                rijndael.IV = Encoding.UTF8.GetBytes(AES_IV_256);
                rijndael.Key = Encoding.UTF8.GetBytes(AES_Key_256);

                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;
            }
        }
    }
}

初期化ベクトルや暗号化鍵は秘密にしておく必要があります。
これを変更すると生成される暗号文字列が変わり、復号にもこれを使います。

リリースビルドではLogファイルは隠しファイルにします。

#if !DEBUG
            System.IO.FileAttributes attr = System.IO.File.GetAttributes(st + ".log");
            System.IO.File.SetAttributes(st + ".log", attr | System.IO.FileAttributes.Hidden);
#endif

Logファイルが増えてくるとストレージを圧迫してしまうので時間や日にちで更新日を確認し、古いファイルは削除していきます。

        private void deleteFiles()
        {
#if !DEBUG
            string cDir = System.Environment.CurrentDirectory;
            string[] files = System.IO.Directory.GetFiles(cDir, "*.log", System.IO.SearchOption.AllDirectories);
            for (int i = 0; i < files.Length; i++)
            {
                DateTime lastWriteTime = System.IO.File.GetLastWriteTime(files[i]);
                if (DateTime.Now - lastWriteTime > TimeSpan.FromMinutes(1))
                {
                    //FileInfoオブジェクトを作成する
                    System.IO.FileInfo fi = new System.IO.FileInfo(files[i]);
                    //ファイルを削除
                    fi.Delete();
                }
            }
#endif
        }

リリース後、Logファイルが必要なときは隠しファイルを解除してLogファイルを出現させます。

private void buttonOutputLog_Click(object sender, EventArgs e)
{
    //隠しファイルを出現させる
    string cDir = System.Environment.CurrentDirectory;
    string[] files = System.IO.Directory.GetFiles(cDir, "*.log", System.IO.SearchOption.AllDirectories);
    for (int i = 0; i < files.Length; i++)
    {
        System.IO.FileAttributes attr = System.IO.File.GetAttributes(files[i]);
        //読み取り専用属性を削除する
        System.IO.File.SetAttributes(files[i], attr & (~System.IO.FileAttributes.Hidden));
    }
}

まとめ

C#のLog出力を使ってみました。
また暗号化や隠しファイルの設定も使ってみました。