20191127のC#に関する記事は8件です。

C# - ちょー雑な方法でFormのアイコンを生成する (Windows)

画面キャプチャ

image.png

サンプルコード

using System;
using System.Drawing;
using System.Windows.Forms;
//using System.Runtime.InteropServices;

class IconMakeTest:Form
{
//  class NativeMethods
//  {
//      [DllImport("user32.dll", CharSet = CharSet.Auto)]
//      public extern static bool DestroyIcon(IntPtr handle);
//  }

    IconMakeTest()
    {
        GetHiconExample();
    }

    static string[] iconDot = new string[]{
        "................",
        ".###...##...##..",
        "..#...#..#.#..#.",
        "..#...#....#..#.",
        "..#...#....#..#.",
        "..#...#....#..#.",
        "..#...#....#..#.",
        "..#...#....#..#.",
        "..#...#....#..#.",
        "..#...#....#..#.",
        "..#...#....#..#.",
        "..#...#....#..#.",
        "..#...#....#..#.",
        "..#...#..#.#..#.",
        ".###...##...##..",
        "................",
    };

    void GetHiconExample()
    {
        using ( Bitmap bmp = new Bitmap(16,16) ) {
            using ( Graphics g = Graphics.FromImage(bmp) ) {
                g.Clear(Color.White);
            }
            for(int y=0;y<16;y++){
                for(int x=0;x<16;x++){
                    if (iconDot[y][x]=='#') {
                        bmp.SetPixel(x,y,Color.Black);
                    }
                }
            }

            IntPtr Hicon = bmp.GetHicon();
            Icon newIcon = Icon.FromHandle(Hicon);

            this.Icon = newIcon;
            //NativeMethods.DestroyIcon(newIcon.Handle);
        }
    }

    [STAThread]
    static void Main(string[] args)
    {
        Application.Run(new IconMakeTest());
    }
}

参考サイト

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

「MonKey - Productivity Commands」のコマンド操作でUnity開発を効率化する

PONOS Advent Calendar 2019の2日目の記事です。

昨日は@honeniqさんのfish shellのはじめかた 2019冬でした。

はじめに

Asset Storeで販売されているアセット「MonKey - Productivity Commands」を導入することにより、コマンドでUnityの各種操作を実行できるようになります。
Unityエディタ上での開発速度の向上が期待できると感じたので、紹介したいと思います。

なお、

  • Unity 2019.2.1.12f1
  • MacOS 10.14.6

の環境で動作確認しています。

「MonKey - Productivity Commands」とは

assetstore.png
MonKey - Productivity Commands - Asset Store

  • 1シート$40
  • 対応するUnityバージョンは2017.4.1以上
  • コマンド操作で様々な機能を実行して開発できる。
  • デザイナー、アーティスト、プログラマのための130個以上のコマンドを用意。
  • 属性を使うことでカスタムコマンドの追加も可能。
  • 公式ドキュメント(MonKey - User Guide)

導入

  • Unity Asset Storeで購入後、プロジェクトにインポートしてください。
    (デモが不要であれば、Plugins/Monkey Commander/Demo!は削除して構いません)
  • インポートが完了するとGetting Startedのダイアログが起動しますが、使い始めるために追加設定は必要ないので、
    そのまま閉じてしまいましょう。
    getting_started.png
  • 以上で導入は完了です。非常に簡単ですね。
  • 「`」キーを入力すると、Monkeyのコマンドパレットが表示され、コマンドの入力が可能になります。
    monkey.png

基本的な使い方

  • コマンドパレットに検索文字列を入力するとコマンドの候補が表示されます。

    • 省略形のコマンドも用意されているので、頻繁に使うコマンドについてはこれを覚えると入力を短縮できます。
    • 一部のコマンドにはホットキーも用意されています。これらのホットキーはMonkeyのコマンドパレットを表示していなくても利用できます。
      select_command.png
  • 「↑」「↓」キーで候補の中から実行するコマンドを選択、「Enter」キーでコマンドを実行します。

    • コマンドによっては実行時に追加のパラメータ入力が必要になるものもあります。
    • 操作感はMacアプリケーションの「Alfred」やUnityアセットの「Quick Search」に近いので、これらのアプリケーションを利用している方は馴染みやすいと思います。

使用例:Imageコンポーネントを持つGameObjectの名前の末尾に「_IMG」を追加する

ここからはMonKeyを活用する一例を紹介します。
以下のようなシーンをサンプルに、Imageコンポーネントを持つすべてのGameObjectについて、それが「イメージを表示しているオブジェクト」であると認識しやすいように、名前の末尾に「_IMG」を追加してみましょう。
Screen_1.png
※Man、Woman、Cat、DogのGameObjectがそれぞれImageコンポーネントを持っています。

この処理を通常の手順で実現しようとする場合、名前の変更を1GameObjectずつ手作業で行わなくてはならず、非常に手間がかかります。しかし、MonKeyを使用することで、その手順を大幅に短縮することが可能となります。
今回の使用例で使用するコマンドは以下の2つです。

Select Scene Objects of Type
指定した型を持つGameObjectをすべて選択します。
省略コマンドとして「ST」が用意されています。

Rename Add Name Suffixes
選択されているすべてのGameObjectの名前の末尾に指定した文字列を連結します。
省略コマンドとして「RNS」が用意されています。

では、MonKeyを使用した場合の手順を見てみましょう。

  1. 「`」でMonKeyのコマンドパレットを開く。
  2. 「ST」を入力し、「Select Scene Objects of Type」コマンドを選択する。
    Screen_2.png
  3. 「Select Scene Objects of Type」コマンドの追加パラメータ入力として、型名の入力フィールドが表示されるので、「image」を入力してコマンドを実行する。
    Screen_3.png
  4. ここまでで、Imageコンポーネントを持つGameObjectだけをすべて選択できた状態となる。
    Screen_4.png
  5. 「`」で再度MonKeyのコマンドパレットを開く。
  6. 「RNS」を入力し、「Rename Add Name Suffixes」コマンドを選択する。
    Screen_5.png
  7. 「Rename Add Name Suffixes」コマンドの追加パラメータ入力として、連結する文字列の入力フィールドが表示されるので、「_IMG」を入力してコマンドを実行する。
    Screen_6.png
  8. 選択中のすべてのGameObjectの名前が「〜_IMG」の形に変更される。 Screen_7.png

以上です。
1個1個のGameObjectを選択して名前を編集する必要がないため、非常に効率的です。

おわりに

本来、複数の操作を組み合わせなければ実現できない処理でも、MonKeyを活用することで1コマンドで実行することができるようになります。
日頃頻繁に実行している処理ほど操作時間や手順が短縮されることによる恩恵は大きいので、

「昨日も同じ手順で面倒な処理をしていた」

と心当たりがある方は導入を検討してみてはいかがでしょうか。

明日は@blockさんです!

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

和暦の環境で西暦の日付の文字列をパースする

問題

Windowsのカレンダーを和暦にした状態でyyyy/MM/dd HH:mm:ss形式の西暦の文字列をDateTime.Parse()でパースすると、yyyyの部分が元号の年として認識されてしまいます。

 var date = "2019/11/21 01:23:45";
 Console.WriteLine(DateTime.Parse(date)); //令和2019/11/21 1:23:45

解決方法

DateTime.Parse()に特定のカルチャに依存しないカルチャ情報(CultureInfo.InvariantCulture)を指定してあげると、yyyyの部分を西暦として認識してくれます。

 var date = "2019/11/21 01:23:45";
 Console.WriteLine(
   DateTime.Parse(date, CultureInfo.InvariantCulture)
 ); //令和1/11/21 1:23:45
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

和暦の環境で西暦の日付の文字列をパースする/DateTime変数を西暦の文字列に変換する

問題

Windowsのカレンダーを和暦にした状態でyyyy/MM/dd HH:mm:ss形式の西暦の文字列をDateTime.Parse()でパースすると、yyyyの部分が元号の年として認識されてしまいます。
また、DateTime型の変数を単純にToString()すると、和暦の文字列が得られてしまいます。

 var date = "2019/11/21 01:23:45";
 Console.WriteLine(DateTime.Parse(date)); //令和2019/11/21 1:23:45

 var date2 = DateTime.Parse("令和1/11/21 01:23:45");
 Console.WriteLine(date2.ToString("yyyy/MM/dd HH:mm:ss")); //01/11/21 01:23:45

解決方法

DateTime.Parse()およびToString()に特定のカルチャに依存しないカルチャ情報(CultureInfo.InvariantCulture)を指定してあげると、yyyyの部分を西暦として認識してくれます。

 var date = "2019/11/21 01:23:45";
 Console.WriteLine(
   DateTime.Parse(date, CultureInfo.InvariantCulture)
 ); //令和1/11/21 1:23:45

 var date2 = DateTime.Parse("令和1/11/21 01:23:45");
 Console.WriteLine(
   date2.ToString("yyyy/MM/dd HH:mm:ss", CultureInfo.InvariantCulture)
 ); //2019/11/21 01:23:45
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity(C#),Python】API通信勉強メモ②Flaskでローカルサーバー立ち上げ

今回やること

この記事は前回の勉強の続きです。
【前回】:【Unity(C#)】API通信勉強メモ①

前回できなかったPostの処理を学びます。

なんもわからんなりの解釈が山盛りなのでマサカリ、オールオッケーです。

Flask

Flask(フラスク)は、プログラミング言語Python用の、軽量なウェブアプリケーションフレームワークである。標準で提供する機能を最小限に保っているため、自身を「マイクロフレームワーク」と呼んでいる。

Wikipediaより引用

今回はFlaskというPythonのウェブアプリケーションフレームワーク1を使用して
ローカルにアプリケーションサーバーを立て、Unity側からHTTPリクエスト(POST)を送る
までやってみようと思います。

おおよそ、参考リンクに沿って進めていきます。

【参考リンク】:Pythonと連携する方法

Flaskのインストール

$pip install flask

GETリクエスト

まずはFlaskで立てたローカルのサーバーにGETリクエストを送ってみます。

Python側の処理
from flask import Flask

app = Flask(__name__)


@app.route("/", methods=['GET'])
def index():
    return "HIKAKIN「ブンブン!ハローユーチューブ!」"


if __name__ == "__main__":
    app.debug = True
    app.run()

Unity側の処理は下記リンクから拝借しました。
【参考リンク】:UnityでHTTPに接続する

Unity側でHTTPリクエストを送るスクリプト
using System.Collections;
using UnityEngine.Networking;
using UnityEngine;

public class HTTPGet : MonoBehaviour {

    //接続するURL
    private const string URL = "http://localhost:5000/";

     void Start()
    {
        //コルーチンを呼び出す
        StartCoroutine("OnSend", URL);
    }

    IEnumerator OnSend(string url)
    {
        //指定したURLでGET
        UnityWebRequest webRequest = UnityWebRequest.Get(url);

        //URLに接続して結果が戻ってくるまで待機
        yield return webRequest.SendWebRequest();

        //エラーが出ていないかチェック
        if (webRequest.isNetworkError)
        {
            //通信失敗
            Debug.Log(webRequest.error);
        }
        else
        {
            //通信成功
            Debug.Log("Get" + " : "+webRequest.downloadHandler.text);
        }
    }
}

GetHIKAKIN.gif

成功しました。
①Python側(VSC)でデバッグを開始
②UnityでPlay
という順番です。

POSTリクエスト

次はFlaskで立てたローカルのサーバーにPOSTリクエストを送ってみます。
@app.route("/", methods=['POST', 'GET'])にPOSTを追加しただけです。

from flask import Flask

app = Flask(__name__)


@app.route("/", methods=['POST', 'GET'])
def index():
    return "HIKAKIN「ブンブン!ハローユーチューブ!」"


if __name__ == "__main__":
    app.debug = True
    app.run()

実際にやってみて思ったこととしては結局POSTとGETは何が違うんだよってことです。
この疑問に関しては下記リンクを見て一旦深く考えるのを止めることにしました。
【引用元】:GETとPOSTの違いについて

本当はGETでやるべきこともPOSTでできてしまうのです。
やろうと思えばすべての処理をPOSTで作れてしまうのです。
この辺りは思想的な話もあるので、何とも言えない部分です。

次回やること

Postの処理も上手くいったので次回はUnity側から入力した情報を登録 & ログインする機能を作っていきます。

とりあえずわかっていることとしては、
リクエストを送る際に情報を渡す方法として、
Unity側にWWW.Formが用意されていることです。

    //POSTする情報
    WWWForm form = new WWWForm();
    form.AddField("user_id", idInputField.text, Encoding.UTF8);
    form.AddField("password",passInputField.text, Encoding.UTF8);

上記のようにUnity側でログイン画面に入力したIDやパスワードをサーバーに送れる。。。はずです。


やりました→【Unity(C#),Python】API通信勉強メモ③簡易版ログイン機能の実装

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

C# usingステートメント

ファイル操作でよく使う
参考記事:https://www.sejuku.net/blog/43105

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

[C#] Publishされた単一EXEが展開する先の一時フォルダのパス取得

地味に困ったので投稿。

Assembly.GetExecutingAssembly().Location

以上のコードを、展開された先で実行されるdll上で実行すれば取得できる。

Environment.GetCommandLineArgs()[0]

因みに、展開元のパスは基本的に上のコードで取得している。

<PublishSingleFile>true</PublishSingleFile>

現状、上のタグを追加してPublishされた単一EXEファイルを実行する際の展開先には、dllのみが展開されるだけで、pbdファイルが含まれていない。
それだとエラー時の詳細情報が取得できないので、それらを取得するためのpbdファイルを移動するために展開先のパスが必要だった。
何故、pdbファイルが展開されないのかは不明。。。

知っている方がいれば、教えていただけると幸いです。

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

C#でAES暗号化アルゴリズムを外部ライブラリに一切頼らず完全実装してみた

はじめに

今回、暗号化技術「AES」(Advanced Encryption Standard)について、深く知り学びたいとふと思ってしまったので、C#でアルゴリズムを完全再現してみました。
今回は学習目的ですが、本来であれば標準ライブラリに頼るべきです。

また、実装にあたって高度な数学は避けられません。
私の範囲を超えたものは、基本的に外部記事に解説を委ねます。

今回、以下の記事を参考にさせて頂きました。

@tobira-code「AESを理解する」

Wikipedia様:
出典: フリー百科事典『ウィキペディア(Wikipedia)』
Advanced Encryption Standard
Rijndael S-box
Rijndael MixColumns
AES key schedule

Wikiwand様:
Block cipher mode of operation

AppliedGo様:
(YouTube)AES Rijndael Cipher explained as a Flash animation

The Advanced Encryption Standard (Rijndael)

AESとは何か

※あくまで主観です
今の時代、Googleで探せばいくらでも情報が手に入りますが、
これに関しては、既知の情報をコピー&ペーストしたような情報記事ばかりで、正確でわかりやすい情報を見つけるのに苦労しました。

AESとは、アメリカ国立標準技術研究所で標準暗号とされている共通鍵暗号アルゴリズムです。
Rijndaelが採用されています。(ラインデールと読むそうです。かっこいい。)

rijnov.gif

画像出典: The Advanced Encryption Standard (Rijndael)

暗号化プロセス

鍵長128bitの場合、ラウンド数は合計10です。
暗号化プロセスは、以下の通りです。
cipherprocess.png

環境

・Visual Studio 2019 Professional
・Windows 10 1903 Build 18362.476
・C#
・.NET Framework 4.7.2

開発期間: 1.5日

実装

SubBytes

S-BOXと呼ばれるテーブルを使用して、バイトを変換します。
S-BOXのテーブルは以下の通りです。
なぜこうなっているのかは私の範囲外ですのでwikipedia様にお任せします。
71fe04fd9ff5679c611feac0c973ac46.png

ソースコード

実際にコードでSBOXの値を求めるにあたって、case-switch文で関数定義しました。
完全に脳死してます。
より良い方法はありますが、とりあえず動作していますし速度は求めていないので今回はこれで行きます。

コメント欄よりご指摘を頂き、ソースコードを修正しました。

SBOX.cs
private static readonly byte[] sbox_table = 
{
    0x63,  0x7c,  0x77,  0x7b,  0xf2,  0x6b,  0x6f,  0xc5,  0x30,  0x01,  0x67,  0x2b,  0xfe,  0xd7,  0xab,  0x76,
    0xca,  0x82,  0xc9,  0x7d,  0xfa,  0x59,  0x47,  0xf0,  0xad,  0xd4,  0xa2,  0xaf,  0x9c,  0xa4,  0x72,  0xc0,
    0xb7,  0xfd,  0x93,  0x26,  0x36,  0x3f,  0xf7,  0xcc,  0x34,  0xa5,  0xe5,  0xf1,  0x71,  0xd8,  0x31,  0x15,
    0x04,  0xc7,  0x23,  0xc3,  0x18,  0x96,  0x05,  0x9a,  0x07,  0x12,  0x80,  0xe2,  0xeb,  0x27,  0xb2,  0x75,
    0x09,  0x83,  0x2c,  0x1a,  0x1b,  0x6e,  0x5a,  0xa0,  0x52,  0x3b,  0xd6,  0xb3,  0x29,  0xe3,  0x2f,  0x84,
    0x53,  0xd1,  0x00,  0xed,  0x20,  0xfc,  0xb1,  0x5b,  0x6a,  0xcb,  0xbe,  0x39,  0x4a,  0x4c,  0x58,  0xcf,
    0xd0,  0xef,  0xaa,  0xfb,  0x43,  0x4d,  0x33,  0x85,  0x45,  0xf9,  0x02,  0x7f,  0x50,  0x3c,  0x9f,  0xa8,
    0x51,  0xa3,  0x40,  0x8f,  0x92,  0x9d,  0x38,  0xf5,  0xbc,  0xb6,  0xda,  0x21,  0x10,  0xff,  0xf3,  0xd2,
    0xcd,  0x0c,  0x13,  0xec,  0x5f,  0x97,  0x44,  0x17,  0xc4,  0xa7,  0x7e,  0x3d,  0x64,  0x5d,  0x19,  0x73,
    0x60,  0x81,  0x4f,  0xdc,  0x22,  0x2a,  0x90,  0x88,  0x46,  0xee,  0xb8,  0x14,  0xde,  0x5e,  0x0b,  0xdb,
    0xe0,  0x32,  0x3a,  0x0a,  0x49,  0x06,  0x24,  0x5c,  0xc2,  0xd3,  0xac,  0x62,  0x91,  0x95,  0xe4,  0x79,
    0xe7,  0xc8,  0x37,  0x6d,  0x8d,  0xd5,  0x4e,  0xa9,  0x6c,  0x56,  0xf4,  0xea,  0x65,  0x7a,  0xae,  0x08,
    0xba,  0x78,  0x25,  0x2e,  0x1c,  0xa6,  0xb4,  0xc6,  0xe8,  0xdd,  0x74,  0x1f,  0x4b,  0xbd,  0x8b,  0x8a,
    0x70,  0x3e,  0xb5,  0x66,  0x48,  0x03,  0xf6,  0x0e,  0x61,  0x35,  0x57,  0xb9,  0x86,  0xc1,  0x1d,  0x9e,
    0xe1,  0xf8,  0x98,  0x11,  0x69,  0xd9,  0x8e,  0x94,  0x9b,  0x1e,  0x87,  0xe9,  0xce,  0x55,  0x28,  0xdf,
    0x8c,  0xa1,  0x89,  0x0d,  0xbf,  0xe6,  0x42,  0x68,  0x41,  0x99,  0x2d,  0x0f,  0xb0,  0x54,  0xbb,  0x16
};

public static byte Convert(byte _byte_)
{
    return sbox_table[_byte_];
}

ShiftRows

4バイト単位の行を一定規則で左シフトします。
例として、以下の画像の通りです。
・2段目を1個左シフト
・3段目を2個左シフト
・4段目を3個左シフト
83e2927a26514dd0981573200c727097.png

ソースコード

1行目は操作の必要がありませんが、わかり易さのために残しています。

Cryption.cs
private static byte[] ShiftRows(byte[] bytes)
{
    byte[] result = new byte[bytes.Length];

    result[ 0] = bytes[ 0]; result[ 1] = bytes[ 1]; result[ 2] = bytes[ 2]; result[ 3] = bytes[ 3];

    result[ 4] = bytes[ 5]; result[ 5] = bytes[ 6]; result[ 6] = bytes[ 7]; result[ 7] = bytes[ 4];

    result[ 8] = bytes[10]; result[ 9] = bytes[11]; result[10] = bytes[ 8]; result[11] = bytes[ 9];

    result[12] = bytes[15]; result[13] = bytes[12]; result[14] = bytes[13]; result[15] = bytes[14];

    return result;
}

MixColumns

Rijindaelのガロア体という、定数を利用して計算を行います。
11256d7b581d0dfd1fdf3501451d2401.png
9c7c22d2ef50400fae38660217d86625.png

具体的には、ガロア体の4つの数字の座標ベクトルにMDS(最大距離分離)行列を乗算します。
これも、高度な数学で私の範囲を超えていますので、外部記事にお任せします。
巡回行列 - Wikipedia
MDS Matrix
MDS行列 の意味・用法を知る

ソースコード

ガロア体は以下のように定義しました。

byte[] matrix = 
{ 
    0x02, 0x03, 0x01, 0x01,
    0x01, 0x02, 0x03, 0x01,
    0x01, 0x01, 0x02, 0x03,
    0x03, 0x01, 0x01, 0x02
};

当ソースコードではわかり易さの為、ガロア体を定数として定義して使用していませんが
どちらにせよコンスタントな値であるため、直接書いても良いです。

Cryption.cs
private static byte[] MixColumns(byte[] bytes)
{
    byte[] result = new byte[bytes.Length];
    byte[,] bytes2d = new byte[4, 4];

    bytes2d = Bytes16To2DBytes4(bytes);

    byte[] matrix = 
    { 
        0x02, 0x03, 0x01, 0x01,
        0x01, 0x02, 0x03, 0x01,
        0x01, 0x01, 0x02, 0x03,
        0x03, 0x01, 0x01, 0x02
    };

    byte[,] matrix2d = new byte[4, 4];
    matrix2d = Bytes16To2DBytes4(matrix);

    byte[,] resultBytes2d = new byte[4, 4];
    for (int c = 0; c <= 3; c++)
    {
        resultBytes2d[0, c] = (byte)((GMul(0x02, bytes2d[0, c])) ^ (GMul(0x03, bytes2d[1, c])) ^ (GMul(0x01, bytes2d[2, c])) ^ (GMul(0x01, bytes2d[3, c])));
        resultBytes2d[1, c] = (byte)((GMul(0x01, bytes2d[0, c])) ^ (GMul(0x02, bytes2d[1, c])) ^ (GMul(0x03, bytes2d[2, c])) ^ (GMul(0x01, bytes2d[3, c])));
        resultBytes2d[2, c] = (byte)((GMul(0x01, bytes2d[0, c])) ^ (GMul(0x01, bytes2d[1, c])) ^ (GMul(0x02, bytes2d[2, c])) ^ (GMul(0x03, bytes2d[3, c])));
        resultBytes2d[3, c] = (byte)((GMul(0x03, bytes2d[0, c])) ^ (GMul(0x01, bytes2d[1, c])) ^ (GMul(0x01, bytes2d[2, c])) ^ (GMul(0x02, bytes2d[3, c])));
    }

    result = Bytes2D4ToBytes16(resultBytes2d);

    return result;
}

MDS(最大距離分離)行列を乗算
式が変換先の型の範囲外の値を生成した場合に、オーバーフローを検出させないために、unchecked()が必要です。

public static byte GMul(byte a, byte b)
{
    byte p = 0;

    for (int counter = 0; counter < 8; counter++)
        if ((b & 1) != 0) p ^= a;

    bool bs = (a & 0x80) != 0;
    a <<= 1;
    if (bs) a ^= unchecked((byte)0x11B);
    b >>= 1;

    return p;
}

KeySchedule : RotWord

ラウンド毎に行われる、新しい鍵の生成です。
まずはじめに、鍵の行(縦)4行目をとり、RotWordという入れ替え処理を行います。
d39a821c59df93eef1dc7245721d9895.png
次にRijindael S-BOX定数を用いて、該当行を変換します。

SBOX.Convert([byte]);

また、RCON(RoundConstant)と呼ばれる、ラウンドコンスタント定数を使用しています。

private static readonly byte[] RCON =
{
    0x00000000, 
    0x00000001, 
    0x00000002, 
    0x00000004, 
    0x00000008, 
    0x00000010, 
    0x00000020, 
    0x00000040, 
    0x00000080,
    0x0000001B,
    0x00000036
};

ソースコード

Cryption.cs
private static void KeySchedule(byte[] bytes, int currentRound)
{
    byte[,] bytes2d = Bytes16To2DBytes4(bytes);
    byte[,] round_key = new byte[4, 4];
    byte[] r = new byte[16];
    byte[] r_ = new byte[16];

    //Rot Word
    //一旦値を格納
    r[ 3]  = CURRENT_CHIPHER_KEY[ 3]; //09
    r[ 7]  = CURRENT_CHIPHER_KEY[ 7]; //cf
    r[11]  = CURRENT_CHIPHER_KEY[11]; //4f
    r[15]  = CURRENT_CHIPHER_KEY[15]; //3c
    //↓ 一番下を一番上に持ってくる
    r_[ 3] = CURRENT_CHIPHER_KEY[ 7]; //cf
    r_[ 7] = CURRENT_CHIPHER_KEY[11]; //4f
    r_[11] = CURRENT_CHIPHER_KEY[15]; //3c
    r_[15] = CURRENT_CHIPHER_KEY[ 3]; //09
    //↓ s-box変換
    r_[ 3] = SBOX.Convert(r_[ 3]); //8a
    r_[ 7] = SBOX.Convert(r_[ 7]); //84
    r_[11] = SBOX.Convert(r_[11]); //eb
    r_[15] = SBOX.Convert(r_[15]); //01

    r[ 0]  = CURRENT_CHIPHER_KEY[ 0];
    r[ 4]  = CURRENT_CHIPHER_KEY[ 4];
    r[ 8]  = CURRENT_CHIPHER_KEY[ 8];
    r[12]  = CURRENT_CHIPHER_KEY[12];

    round_key[0, 0] = (byte)(CURRENT_CHIPHER_KEY[ 0] ^ r_[ 3] ^ RCON[currentRound]);
    round_key[1, 0] = (byte)(CURRENT_CHIPHER_KEY[ 4] ^ r_[ 7] ^ RCON[0]);
    round_key[2, 0] = (byte)(CURRENT_CHIPHER_KEY[ 8] ^ r_[11] ^ RCON[0]);
    round_key[3, 0] = (byte)(CURRENT_CHIPHER_KEY[12] ^ r_[15] ^ RCON[0]);

    for (int x = 0; x <= 3; x++) //0,1,2 (3)
    {
        //0は除きたい(横インデックス1から埋め込んでいく)ので x は 1 2 3 のみに絞る
        if (x == 0) continue;
        round_key[0, x] = (byte)(CURRENT_CHIPHER_KEY[ 0 + (1 * x)] ^ round_key[0, x - 1]);
        round_key[1, x] = (byte)(CURRENT_CHIPHER_KEY[ 4 + (1 * x)] ^ round_key[1, x - 1]);
        round_key[2, x] = (byte)(CURRENT_CHIPHER_KEY[ 8 + (1 * x)] ^ round_key[2, x - 1]);
        round_key[3, x] = (byte)(CURRENT_CHIPHER_KEY[12 + (1 * x)] ^ round_key[3, x - 1]);
    }

    CURRENT_CHIPHER_KEY = Bytes2D4ToBytes16(round_key);
}

初期ラウンドにやること

初期ラウンドのAddRoundKeyでは、暗号化するByteと鍵とのXorをとります。

byte[] result = new byte[bytes.Length];
for (int x = 0; x < bytes.Length; x++)
{
    result[x] = (byte)(bytes[x] ^ CIPHER_KEY[x]);
}
暗号化するByte:
32 88 31 e0
43 5a 31 37
f6 30 98 07
a8 8d a2 34

鍵:
2b 28 ab 09
7e ae f7 cf
15 d2 15 4f
16 a6 88 3C

結果:
19 A0 9A E9
3D F4 C6 F8
E3 E2 8D 48
BE 2B 2A 08

結果

ラウンド1

round1.png

ラウンド2

round2.png

ラウンド3

round3.png

ラウンド4

round4.png

ラウンド5

round5.png

ラウンド6

round6.png

ラウンド7

round7.png

ラウンド8

round8.png

ラウンド9

round9.png

ラウンド10

round10.png

無事、暗号化に成功しました。

最後に

今回は、AES暗号化アルゴリズムを外部ライブラリに頼らずフル実装してみました。
解説に関しては、私が高校数学を未だ習っていない為外部に頼りきりですが、プロジェクトを通して、本当に沢山のことを学べました。
また、当プロジェクトでは暗号化のみ行っていますが、また機会があれば復号化にもチャレンジしてみたいです。

今回のプロジェクトは、Githubリポジトリよりフルソースコードを閲覧できます。
GitHub

当記事に間違いや訂正するべき点がございましたら、お手数ですがコメント欄よりご指摘を頂けますと幸いです。

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