20190716のC#に関する記事は3件です。

[C#]データをAESで暗号化してファイルに保存する

やりたいこと

テキストとして読まれたくないが、ファイル保存しておきたい文字列があるときに、暗号化してファイル保存して、使うときに復号化するようなことがしたい。

やり方

AesCngというクラスを使用して、暗号化・復号化を行う。

サンプルコード

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace ConsoleApp9
{
    class Program
    {
        static void Main(string[] args)
        {
            // "あいうえお"を暗号化
            AesEncrypter.WriteAesEncryptedBytesToFile("あいうえお", @"C:\work\test.bin");

            // 暗号化したデータを読み出し復号化する
            var d = AesEncrypter.ReadAesEncryptedBytesFromFile(@"C:\work\test.bin");

            Console.WriteLine(d);
            Console.ReadLine();
        }
    }

    public class AesEncrypter
    {
        ///IV 半角16文字のランダムな文字列
        private static readonly string AesIV = @"xxxxxxxxxxxxxxxx";

        // キー 半角32文字のランダムな文字列
        // (1文字あたり8bit→8*32=256bit→キーサイズ)
        private static readonly string AesKey = @"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

        // キーサイズ(bit)
        private static readonly int KeySize = 256;

        // ブロックサイズ
        private static readonly int BlockSize = 128;

        // 暗号化
        public static byte[] Encrypt(string value)
        {
            // AESオブジェクトを取得
            var aes = GetAesCng();

            // 対象の文字列をバイトデータに変換
            var byteValue = Encoding.UTF8.GetBytes(value);

            // バイトデータの長さを取得
            var byteLength = byteValue.Length;

            // 暗号化オブジェクトを取得
            var encryptor = aes.CreateEncryptor();

            // 暗号化
            return encryptor.TransformFinalBlock(byteValue, 0, byteLength);
        }

        // 復号化
        public static string Decrypt(byte[] encryptValue)
        {
            // AESオブジェクトを取得
            var aes = GetAesCng();

            // 復号化オブジェクトを取得
            var decryptor = aes.CreateDecryptor();

            // 復号化
            var decryptValue = decryptor.TransformFinalBlock(encryptValue, 0, encryptValue.Length);

            // 復号化されたバイトデータを文字列に変換
            var stringValue = Encoding.UTF8.GetString(decryptValue);

            return stringValue;
        }

        // AESオブジェクトを取得
        private static AesCng GetAesCng()
        {
            // AESオブジェクトを生成し、パラメータを設定します。
            var aes = new AesCng();
            aes.KeySize = KeySize;
            aes.BlockSize = BlockSize;
            aes.Mode = CipherMode.CBC;
            aes.IV = Encoding.UTF8.GetBytes(AesIV);
            aes.Key = Encoding.UTF8.GetBytes(AesKey);
            aes.Padding = PaddingMode.PKCS7;

            return aes;
        }

        // 文字列を暗号化してファイルに書き出す
        // targetString 暗号化したい文字列
        // keyFilePath 暗号化した文字列を書き込むファイル
        public static void WriteAesEncryptedBytesToFile(string targetString, string keyFilePath)
        {
            byte[] encrypted;

            try
            {
                encrypted = Encrypt(targetString);

                string folderPath = Path.GetDirectoryName(keyFilePath);
                if (!Directory.Exists(folderPath))
                {
                    Directory.CreateDirectory(folderPath);
                }

                using (var fs = new FileStream(keyFilePath, FileMode.Create))
                using (var bw = new BinaryWriter(fs))
                {
                    bw.Write(encrypted);
                }
            }
            catch
            {
                throw;
            }
        }

        // 暗号化されたファイルからキーを読を復号して返す
        // keyFilePath キーファイルパス
        // return : 復号化された文字列
        public static string ReadAesEncryptedBytesFromFile(string keyFilePath)
        {
            string roundtrip;

            try
            {
                using (var fs = new FileStream(keyFilePath, FileMode.Open))
                using (var br = new BinaryReader(fs))
                {
                    var encrypted = new byte[fs.Length];
                    br.Read(encrypted, 0, (int)fs.Length);

                    roundtrip = Decrypt(encrypted);
                }
            }
            catch
            {
                throw;
            }

            return roundtrip;
        }
    }
}

注意

暗号化の際、IVやキーが必要になるため、コードの中に「AesIV」「AesKey」として埋め込まれているが、キーなどの値がわかってしまうと、簡単に解読(復号)されてしまう。
.NETのコードは、ILSpyなどのツールを使うと簡単にコードが読めるようにできてしまうため、このコードをこのまま使うようなら、難読化などの対策が必要。

メモ

下記のクラスでも、同じようにAESの暗号化が行える

  • AesCryptoServiceProvider(.NET Framework3.5以降で使用できるクラス。)
  • AesManaged(.NET Framework3.5以降で使用できるクラス。これはマネージドコードで全部書かれてるらしい)
  • AesCng(.NET Framework4.6.2以降で使用できる、現段階(2019年)で新しいクラス。)

また、上記のAesCngクラスの継承元のAesクラスの、さらに継承元のSymmetricAlgorithmというクラスがある。これを継承して、AESだけでなく、DES、RC2、Rijndael、TripleDESなどの暗号化用のクラスが用意されている。

参考

AesCng Class
https://docs.microsoft.com/ja-jp/dotnet/api/system.security.cryptography.aescng?view=netframework-4.8

C#で文字列を暗号化・復号化する。
ほぼほぼこちらを参考にさせてもらってます。
https://paveway.hatenablog.com/entry/2019/04/08/csharp_encrypt

Rijndaelクラスを使ったAES暗号化
https://qiita.com/kz-rv04/items/62a56bd4cd149e36ca70

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Xaml Island] 【うまく動かせず断念】.NET core 3.0のWPFでUWPコントロールを使う

やりたいことと、現状のおことわり

お題のとおり、.NET core 3.0のWPFでUWPコントロールを使ってみようとしています。
内容は、勉強のための、かずきさんブログのトレースです。

ただ、かずきさんブログを参考にInkCanvasを試しては見たのですが、現状うまく動かすことができませんでした。
感触としては、HDD容量の節約のために、インストールしていないSDKやツールが多数あるせいで、なにかがうまく動いていないような(個人的な)感触です。

なので、下記をそのまま実施しても、うまく動かない可能性が高いです。
今後、再度トライしたときにメンテしようと思うのと、うまくいかない時の事例としてもしかして参考になればと思い、一応書いた分は残します。(見た方を混乱させてしまったら申し訳ありません)

基本、やったことを上から順番に書いてますが、一番最後に、つまった部分をまとめて書いてます。(うまくいかなくてごちゃごちゃいじくりまわしたので、正確でないかもしれません)

前提

下記のものを使って、2019年7月上旬に試した。
VisualStudio Community2019 Version 16.1.5
.NET Core3.0 preview5
Microsoft.Toolkit.Wpf.UI.Controls 6.0.0-preview5

やったこと

.NET core3.0のインストール

こちらから、インストールした。
https://dotnet.microsoft.com/download/dotnet-core/3.0

試した当時、最新の.NET core3.0はpreview6。(今回試したのは5)
本リリースは2019年11月とのこと。

VisualStudio2019の設定

VisualStudio2019を起動し、
[ツール] > [環境] > [プレビュー機能] > [.NETコアSDKのプレビューを使用する]
にチェックを入れる。
image.png

MSの公式ページ?には、
[ツール] > [プロジェクトおよびソリューション] > [.NET Core] >[Use Preview of the .NET core SDK]
にチェックを入れろ、とあるが、そこにはチェックはなかった...
(このページはVS2017の話??)

Microsoft.Windows.SDK.ContractsをNugetからとる

★★今回xaml islandをするうえでは、Microsoft.Windows.SDK.Contractsは不要。
★★下で入れるMicrosoft.Toolkit.Wpf.UI.Controlsに、含まれている。
★★消すのがもったいないので書いた内容は置いておきますが、読み飛ばして下さい。

Windows10 APIを使用するために必要。
試した時点では、Ver 10.0.18362.2002-preview ※プレビュー版

以前は、もう少しややこしい手順が必要だったのが、簡単になった。
ややこしい手順=たぶんこれのこと。

image.png

NugetにあるMicrosoft.Windows.SDK.Contractsの説明を訳すと、

Windows 10 WinRT API Packを使用すると、.NET Framework 4.5以降および.NET Core 3.0以降の
ライブラリおよびアプリケーションに最新のWindowsランタイムAPIサポートを追加できます。

このパッケージには、Windows 10バージョン1903までのサポートされている
すべてのWindowsランタイムAPIが含まれています。

とのこと。

Microsoft.Toolkit.Wpf.UI.ControlsをNugetからとる

UWPのコントロールをWPFで使うために必要。
試した時点では Ver 6.0.0-preview5 ※プレビュー版

image.png

Program.csを追加する

下記の手順で、追加したProgram.csをスタートアッププログラムに指定し、
スタートアップに指定する。

  • ソリューションを右クリック> [追加] > [新しい項目] > [クラス]を選択し、Program.csと名付ける

- 下のコードを記述する。

Program.cs
using Microsoft.Toolkit.Win32.UI.XamlHost;
using System;

namespace WpfApp30
{
    class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            using (new XamlApplication())
            {
                var app = new App();
                app.InitializeComponent();
                app.Run();
            }
        }
    }
}

  • ソリューションのプロパティ > [アプリケーション]の中の[スタートアップオブジェクト]で、作成したProgramクラスを指定する image.png

マニフェストファイルの作成

ソリューションを右クリック> [追加] > [新しい項目] > [アプリケーションマニフェストファイル]を選択し、マニフェストファイルを追加する。(今回は名前は適当)
image.png

マニフェストファイルの修正

作成したマニフェストファイルを開き、下記を行う。

  • Windows10が対象OSであることを示すために、Windows10のところのコメントアウトを解除する
  • 対象のバージョンが19H1以降であることを示すために、maxversiontestedを追加して、 ※これをしないと、xaml islandのライブラリが例外を吐くらしい。(かずきさんページ)
  • dpiAwareの設定を追加する

変更前

  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
    <application>
   ・・・
      <!-- Windows 10 -->
      <!--<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />-->

    </application>
  </compatibility>
   ・・・
  <!--
  <application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
    </windowsSettings>
  </application>
  -->

変更後

  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
    <application>
   ・・・
      <!-- Windows 10 -->
      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
      <maxversiontested Id="10.0.18362.0"/>

    </application>
  </compatibility>
   ・・・
  <application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
    </windowsSettings>
  </application>

マニフェストファイルの指定

ソリューションのプロパティ > [アプリケーション]の中の[リソース] > [マニフェスト]で、作成したマニフェストを指定する。(自動で設定されているかも)
image.png

xamlに、UWPのコントロールを配置する

  • まず、xaml Islandの名前空間を追加する
  • その後、下記のようにコードを書き、UWPのコントロールを配置する(今回は、UWPのInkCanvasを置く)
MainWindow.xaml
<Window x:Class="WpfApp33.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp33"
        xmlns:toolkit="clr-namespace:Microsoft.Toolkit.Wpf.UI.Controls;assembly=Microsoft.Toolkit.Wpf.UI.Controls"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <toolkit:InkCanvas  />
    </Grid>
</Window>

ここまでの通りやってみましたが、うまくうごかなかった..

うまく動かない、というのは、ビルドはできて、アプリ実行(F5)もできるが、ウインドウになにも表示されない状態になる(InkCanvasはもちろん、試しにその他配置したもの(Buttonなど)もすべて見えない)、という感じ。
(InkCanvasをはずすと、その他のButtonなどは出てくる)

しばらく寝かせて、近く再トライしたい。
image.png

かずきさんデモ/ブログのとおりにやってみて、つまづいた部分

  • 実験当時、Microsoft.Toolkit.Wpf.UI.Controlsは最新は6.0.0-preview6.3だったが、そのVerだとProgram.csがうまくビルドできなかったので、かずきさんがデモされているpreview5を使用した。(preview6.3だとXamlApplicationクラスがnewできない)
  • .NETCore3.0preview6とMicrosoft.Toolkit.Wpf.UI.Controls 6.0.0-preview5の組み合わせだと、こんなエラーがでる??
  • image.png
  • 上のエラーは、.NETcore3.0とNugetしたtoolkitのバージョンの組み合わせではなく、Windows10のバージョンが新しくないため???こちら参照(When I run the application, I get an InvalidCastException. とあるところ)
  • 今使っていたWindows10は「1809」だった。どうも「1903」以降でないとうごかないっぽい。
  • image.png
  • 1809→1903にWindowsUpdateしてみて試す→Updateしたが、だめだった。状況変わらず。
  • 「ユニバーサルWindowsプラットフォーム開発」のWindows 10 SDK (10.0.18362.0)がインストールされていない。SDKが足りていないから動かないのかも?→インストールしたが、動かず。(ビルドはできて、アプリ実行もできるが、ウインドウになにも表示されない状態になる(InkCanvasはもちろん、その他に配置したものもすべて見えない))
  • .NET core3.0のせいかも?→.NET Framework(たしか4.7.2)に変えて同じことをやってみたが、同じような現象になった。(ビルドできてWindowはでるが、真っ白なWindowになる)
  • ここまでで、一旦断念...

その他不明点メモ

  • Windows10 API = UWP APIなのか??用語の理解が追い付いてない。
  • あとWindows Runtime APIも。同じものを指してる??

参考

公式ページ
https://docs.microsoft.com/ja-jp/windows/apps/desktop/modernize/xaml-islands

公式ブログ
https://techcommunity.microsoft.com/t5/Windows-Dev-AppConsult/Using-XAML-Islands-on-Windows-10-19H1-fixing-the-quot/ba-p/376330#M117

https://www.thomasclaudiushuber.com/2019/04/23/net-core-3-use-uwp-controls-in-wpf-with-xaml-islands/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

.NET Core上で実数演算すると結果がズレる

TL;DR

.NET Core(2.2)上で倍精度浮動小数点を使った演算を行うと、同じ順序で計算させても環境によって計算結果がズレるから、比較するときとか注意すべきではないかと考える1

実際やったこと

実行したテストの内容は以下の通り

  1. [-90°,+90°]の区間を1°につき128分割してその弧度を計算した。
  2. 予め計算されている上記に対応した弧度を元にして、sin,cos,tan及び、sin/cosを標準のSystem.Mathを使い計算した。

弧度の計算方法

弧度の計算は、以下の二種類を利用した。以下、対応するDegreesをdとする。

  1. 単純にd*(Math.Pi/180)を計算させた。
  2. jglmを参考に倍精度浮動小数点の上位32bitを分割した上で、仮数域を拡張して、高精度計算を行った2

実行環境一覧

今回も多数のご協力を得る事が出来た。
また、この検証を進めるに当たり、多くの方から資料の提示や、アドバイスを頂くことが出来た。
この場を借りてお礼申し上げます。

テストした実行環境は以下の通りとなる。

test_env

計算させた結果に関する考察

さて、実際計算させた結果、割と面白い結果になった。

この先示す図表で、は差異が出なかったことを示し、×は差異が存在したことを示している。

Windowsの場合

Windowsは唯一.NET Coreの32bit環境が存在する。そこで、32bit環境と、64bit環境を各々で比較してみたところ、以下のようになった。

32bit Process windows

実行結果は以下の通り。

32Win

上記結果のように32bit版Windowsでは環境によって今回差異が出なかった。

これは恐らく、浮動小数点演算をx87 FPUで行っており、三角関数は各々、fsinfcosを使って演算していたので、差異が出なかった者と考えられる。

但し、主にAMDとIntelで歴史的な経緯により実装に差異があるとのことだったので3、他の計算を行った場合、差異が出てくる可能性があるかも知れない。

64bit Process Windowsの場合

実行結果は以下の通り

64Win

64bit ProcessのWindowsの場合、

FMA3命令を持っているか否かで計算結果に差異が出てくる結果となる。

FMA3の有無で差異が出るのは、[FMA (Fused Multiply-Add) について色んな観点でまとめてみた]によれば、丸めの回数が減るので、結果計算結果に差異が出たのではないかと予想される。

macOSの場合

macOSの場合は以下のようになった。

image.png

上記結果のように、CPUの差異で計算結果に差異が出るようなことはなかった。

但し、CPUが全てFMA3を持っているモデルなので、FMA3を持っていない場合でも、当該環境内における再現性が担保されるか否かは不明である。

Linuxの場合

Linuxの場合以下のような結果になった。

Linux

上記の、i7-3770kと、Ryzen 7 1700xは、WSLのLinux(Ubuntu 18.4)であり、GravitonはAWS上のUbuntu on ARMインスタンスとなっている。

このように、Linuxで有ればアーキテクチャ、CPUの世代、実行環境によって計算結果に差異が出なかった。

又、Linux上のclang及びgccを使用して同一の計算を行った場合の結果を以下に示す。

image.png

このように、clangと、gccを使った場合でも計算結果に差異が出なかったので、恐らく全てのパターンで標準のlibcを使っているのではないかと予想される。

各プラットフォーム間の検証

各プラットフォーム間の検証結果は以下の通りとなった。

grp

このように、各環境間では全ての環境間で差異が出た。

まとめ

以前の検証でILがASMに変換された結果を調査していたとき、X86とX64で実数計算をx86はFPU、x64はSIMDで行っていたので、差異が出たらちょっと面白そうだな程度で調べてみたのだが,中々どうして興味深い結果となった。

計算順序が同一であったとしても今回の検証により、実行環境の差異によって計算結果に差異を生じることがあり得ることがわかったので、異なる環境間で計算を行いその比較を行う際は、注意が必要では無いかと思う。


  1. とは言え、実用上問題となるほどズレるわけではなく、下何桁かに差異が出てくる形となる。 

  2. 今回のケースでのみ使うことを想定していたので、NaNとInfのチェックはしてたけど、非正規化数か否かのチェックは省いている。(実用性を考えるなら当然行うべきだと思う) 

  3. Cross Platform Floating Point Consistencyに、Again, in case of the x87 instruction set, Intel and AMD have historically implemented things a little differently.とあった。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む