骨董品置場

C#とか学んだことかく

.NET でパスワードによるデータの暗号化の実装

書けてないもの書こうぜ!アドベントカレンダー(仮称)

5/5 担当でした。ごめんなさい。たくりんとん君ごめんなさい。失踪してました。


普段私は(このご時世に)自作のパスワード管理ソフトウェアを使っています。

6年くらい前に作った代物なので WinForms で出来ていたりしますが、いろいろ新機能を追加したくなったので思い切って WPF で作り直すことにしました。

UIなど色々未実装の WIP ですが、以下がそのリポジトリです。

github.com

パスワードのデータをアプリ用のマスターパスワードで復号するという仕様です。

セキュアで非同期なイイ感じにしたいなと思い、書いたコードがこれ

暗号化部分

public async Task<Message> EncryptAsync(string password, byte[] rawData, CancellationToken cancellationToken = default)
{
    cancellationToken.ThrowIfCancellationRequested();
    using var deriveBytes = new Rfc2898DeriveBytes(password, saltSize);
    var salt = deriveBytes.Salt;
    using var encAlg = Aes.Create();
    encAlg.Key = deriveBytes.GetBytes(keySize);
    await using var ms = new MemoryStream();
    await using var cs = new CryptoStream(ms, encAlg.CreateEncryptor(), CryptoStreamMode.Write);
    await cs.WriteAsync(rawData, cancellationToken);
    await cs.FlushFinalBlockAsync(cancellationToken);
    var message = new Message
    {
        EncryptedData = ms.ToArray(),
        Iv = encAlg.IV,
        Salt = salt
    };
    return message;
}

Rfc2898DeriveBytes クラスを使うことで、簡単にパスワードベースでキーとソルトを作ることができます。

デフォルトは SHA1 を使ってるけど、SHA256 ハッシュアルゴリズムにした方が良いのかな?)

あとは

  • AES クラスのファクトリメソッドを使って(using var が使えるの最高に気持ちいいですね) オブジェクトを作る。
  • Key を deriveBytes から格納。キーサイズは 16バイト、つまり128ビットに設定しました。AES のデフォルトなはず。
  • Memory に格納したいので MemoryStream を使って非同期に格納します。
  • 暗号化や復号を入出力に置き換えられるCryptoStreamを使って Write して Flush します。 あとは MemoryStream のデータをbyte配列で受け取って、初期化ベクトルとソルトをそれぞれ保管すれば終わり。

ソルトはパスワードをハッシュする際に、初期化ベクトルは暗号化したデータを毎回違う値にするためのもの。

実際にテストで毎回違う値が生成されているのを確認しています。

ソルトや初期化ベクトルは公開しても良いデータなので、そのままメッセージ内に詰めちゃって大丈夫。

これで暗号化部分が完成。

復号部分

public async Task<byte[]> DecryptAsync(string password, Message message, CancellationToken cancellationToken = default)
{
    cancellationToken.ThrowIfCancellationRequested();
    using var deriveBytes = new Rfc2898DeriveBytes(password, message.Salt);
    using var decAlg = Aes.Create();
    decAlg.Key = deriveBytes.GetBytes(keySize);
    decAlg.IV = message.Iv;
    await using var ms = new MemoryStream();
    await using var cs = new CryptoStream(ms, decAlg.CreateDecryptor(), CryptoStreamMode.Write);
    await cs.WriteAsync(message.EncryptedData, cancellationToken);
    await cs.FlushFinalBlockAsync(cancellationToken);
    return ms.ToArray();
}

同じようにRfc2898DeriveBytes クラスを使い、今度はデータから得たソルトを入れて初期化します。

AES クラスの Key, IV プロパティにそれぞれ取得した値を入れて、暗号化時と同じように MemoryStreamCryptoStream を使ってメモリに対して Write, Flush。

最後にメモリの内容を ToArray() で byte[] で取得して後にオブジェクトに直したりして使うといった感じ。

現状イイ感じにデータの暗号化をするならこんな感じかと思っているので備忘録代わりに書いてみました。

それではまた次のアドベントカレンダー記事で。

【Discord.NET】Discord Bot でどのチャンネルからメッセージが送られてきたか調べる。

書けてないもの書こうぜ!アドベントカレンダー(仮称)

5/2 担当の AntiqueR です。

最近趣味でカスタムコマンド Discord Bot を作ってました(Twitch の Night Bot のような)

github.com

サーバーの ID ごとにコマンド名とリプライを管理して、DB にコマンドが存在していれば返す感じのシンプルなボットです。

開発中に困ったのが、Guild Id (サーバーのID) とコマンドを紐つけて管理しているので、サーバー以外からのメッセージを反応しないようにするにはどうすれば良いかという問題でした。

解決方法

あった

docs.stillu.cc

メッセージの送信元は以下の4つで識別できます。

  • ITextChannel
    • サーバー内のチャット
  • IDMChannel
    • その名の通り DM
  • IGroupChannel
    • DMのグループチャンネル(多分)
  • IPrivateChannel
    • DM または DM のグループチャット

今回はサーバーからのメッセージだけに限定したかったので、ITextChannel かどうかを識別します。

使い方としてはこんな感じ

メッセージハンドラ内の処理

private async Task MessageHandle(SocketMessage message)
{
    if (!(message.Channel is ITextChannel) || !(message is SocketUserMessage msg) || msg.Author.IsBot) return;
}

簡単だった。

アドベントカレンダーに乗っけるものとしては小さめの記事になりましたが、それは次(か来週の記事で)挽回するということで...

PC 環境構築用の個人的メモ

PC 購入時や PC をクリーンした時用に残しておきます。
入れるソフトウェアなんかが書いてあります。個人的な内容も入っています。

ソフトウェア

開発関連

開発補助

  • XAML Controls Gallery
  • Fluent XAML Theme Editor

パッケージマネージャー

  • Scoop

個人用

フォント

  • 源ノ角ゴシック Code JP
  • Cascadia Code PL

自作したやつ

  • hTea
  • CP-You2

設定項目

  • SSH 設定
  • 壁紙
  • ブラウザ同期
  • VS設定
  • 環境変数や、パフォーマンス関連の設定
  • メールアドレス等の辞書登録

補足

  • Think Pad なら Lenovo Vantage も入れる。
  • Powershell の Powerline 設定 => ここを見る
  • なるべく Scoop で管理する。
  • アイコン各種をコピーしておく

SansanのC#サーバーサイドに一ヶ月インターン行ってきた話

閲覧ありがとうございます。AntiqueRっていいます。
普段はC#で色々してます。WPF, UWP, Unityもちょこっとやってます。
初めて3日を超えるインターンに行ったのでブログをカキカキしようと思います。

やったこと

どこまで書いていいのか判断できないので簡単に書くと、

  • 名刺管理画面にメニューを追加して、機能へのショートカットを作る

みたいなことをしてました。
LINQサイコー!ReSharperサイコー!って思いながらお仕事してました。
リリースできて良かった!

触ったもの

を触りました。js触りながら「型は正義」と無限に感じていました。

どうして参加したのか

  • C#だから。

僕は将来C#エンジニアとして活動したいと思ってます。中でも、not Unity な開発をしたいなと思ってます。
そんな時お声をかけて頂き、C#でサーバーサイドは貴重すぎる!行くしかねえ! と思って参加しました。(声をかけて頂いた時、Sansanさんを知らなかったのです。)

インターンが終わっての感想

めちゃくちゃ良い会社

皆さん前向きな方が多いな。という印象を受けました。
特にインターンさせて頂いたチームの方はめちゃくちゃ楽しそうにお仕事してた(気がする)。

よく「趣味を仕事にしてはいけない」と聞きますが、私は「趣味を仕事」にしても楽しく生きて働ける会社に行きたいと思っていたので、とても良い!!!と思いながらお仕事してました。

Slackがめちゃめちゃゆるい感じだったのもとても良いなと思いました。

色んな経験が出来た。

僕はずっと個人開発をしてきたので、チーム開発のノウハウが0に近い状態でした。
チームでのGitの使い方、Pull Requestの出し方等、個人では学べない沢山の事を学べました。
インターン後から本格的にチーム開発をスタートさせようと考えていたので、時期的にも、とても良いタイミングでした。
チーム開発に用いるツール等も知ることができたのはとても大きかったですね。

ランチ最高!

2000円の補助が出るのはでかい! 平日毎日2000円の表参道ランチ最高!

キーボードがアツい

アツい。とてもアツかった。
自作キーボーダーが集まるSlackのチャンネル等もあり、痺れた。
様々な自作キーボードを見せてもらったり、触らせてもらいました。
の、ですが、本当に申し訳ないのですが、私はThinkPadキーボードの方に魅せられてしまいました。
買います。ありがとうございました。

おわりに

ここまで思ったことを (ほぼ無秩序に) 書きならべてきました。
色んな方々とランチさせて頂いたり、仕事を一緒にさせて頂いたことで、生き方、考え方から技術まで、様々なことを学ばせて頂きました。

メンターの北見さん。いつも笑顔で、手にピースを添えて優しく接してくれて、精神的にとても助かりました。
同じチームの方、ランチに行っていただいた方、開発部の皆さん、Sansanに関わる全ての方、 今は感謝の気持ちでいっぱいです。本当に、本当にありがとうございました。

AntiqueR.

XAMLの勉強に詰まったら公式サンプルアプリを使おう。

タイトル通り。
XAMLってどんなコントロールがあって、どんな機能があるのかなかなか把握しにくくないですか?
そんな時僕にふってきたのが公式サンプルという神様でした。

どういうものか

実際に見てもらったほうが早いと思います。

www.microsoft.com

その名の通りUWPコントロールのギャラリーになんですが、ButtonやCheckBox, TextBoxといったベーシックな物から、マウスによるReveal Styleの詳細など、リッチなGUIの組み立てに役立つ情報が多くあります。
XAMLの学習にこれ以上ないほどの良いサンプルアプリだと思いました。
...という紹介記事?でした。

ThinkPadの「Critical Low Battery Error」が解消した話

使用機器

ThinkPad E580

なにがあったか

  1. 電源ボタンを押します
  2. BIOS画面が出ます
  3. 画面が黒くなります
  4. 左上に Critical Low Battery Error と表示されます
  5. 電源が落ちます

電源ボタンをn回押すと正常起動するという「PC起動チャレンジガチャイベント」状態が続きました。

解決方法

簡単にいうとBIOSアップデートで治りました。

  1. Lenovo Vantage(多分デフォルトで入ってる)を起動
  2. システム更新=>更新プログラムの確認 へ移動
  3. 更新プログラムの確認のおして、でてきたものをインストール&再起動
    おわり

ある日突然発生した事なので原因がよく分かっていませんが、まぁこれで解決したので良しとします。

Azure FunctionsでByte配列をなんとかして返す。[C#]

AzureFunctionsでAPIサーバー建ててデータの通信を行っているのですが、JSONでなくByte配列でデータを取りたい事があったのでメモ代わりに残しておきます。

FileContentResultで返す(2019年3月16日追記)

Base64エンコーディングを挟む必要はありませんでした。
コードは至ってシンプルです。
* Azure Functions側

var byteArray = new Byte[]{0,1,2,3};
return new FileContentResult(byteArray);
  • クライアント側
    FileContentResultもHttpStatusはOKを返します。
var res = await HttpClient.GetAsync("https://example.com/api/example");
if (res.StatusCode == HttpStatusCode.OK){
    var byteArray = await res.Content.ReadAsByteArrayAsync()
}
else ...

Base64エンコーディング

Base64エンコーディングでByte配列を文字列に変換します。
Base64についての詳細は割愛します。

  • Azure Functions側
var byteArray = new Byte[]{0,1,2,3};
var baseStr = System.Convert.ToBase64String(byteArray);
return new OkObjectResult(baseStr);
  • クライアント側
    GETメソッドで取得した場合のコードです。
var res = await HttpClient.GetAsync("https://example.com/api/example");
if (res.StatusCode == HttpStatusCode.OK){
    var binStr = System.Convert.FromBase64String(await res.Content.ReadAsStringAsync());
}
else ...