銀の光と碧い空

クラウドなインフラとC#なアプリ開発の狭間にいるエンジニアの日々

ASP.NET Core 3 Web APIで例外をスローして、指定したステータスのレスポンスを返す

ASP.NET Core Web APIでアプリケーションロジック内では例外を投げておいて、共通処理として例外をキャッチして例外に応じたステータスコードとメッセージのレスポンスを返したい場合があるかと思います。ASP.NET Framework やCore 2.2まであったHttpResponseExceptionを使うようなユースケースです。

docs.microsoft.com

ASP.NET Core 3ではなくなっていますが、自前で作ることでより自分のユースケースにあった処理が作れることが次のドキュメントに記載されています。

docs.microsoft.com

今回これをすこしアレンジした使い方をしたので紹介してみます。

続きを読む

DateTimeとTimeSpanの書式文字列が違うという話

知ってればそれだけなのですが、System.DateTimeSystem.TimeSpanでは指定できる書式文字列が異なります。知らずによくわからないエラーを出して一瞬困りました。

たとえば、DateTimeオブジェクトで時間のところで HH:mm:ss という形式で出力したい場合は次のようにカスタム書式文字列を指定できます。

DateTime.Now.ToString("HH:mm:ss")

が、TimeSpanオブジェクトは同じように指定するとエラーになります。

DateTime.Now.TimeOfDay.ToString("HH:mm:ss")

標準のTimeSpan書式文字列のcを指定するか、カスタム書式文字列の場合は時間は小文字のhを使ったうえで:\でエスケープする必要があります*1

DateTime.Now.TimeOfDay.ToString(@"hh\:mm\:ss")
DateTime.Now.TimeOfDay.ToString("c")

なんでこんな使い方をする必要があったかというと、MySQLでDate型のカラムをDateTime型にマッピングして、Time型のカラムをTimeSpan型にマッピングしていたためでした。ドキュメントはこちらにあるのでカルチャーによる違いなど詳細はこちらを参考にしてください。

docs.microsoft.com

docs.microsoft.com

*1:なお標準書式文字列cは1日以上の場合の日付部分と小数点以下の秒も出力されるという違いがあります

.NET Core で暗号論的疑似乱数生成器を使ってソルトを作成し、PBKDF2でパスワードのハッシュ化を試みる

この時代、自前でパスワードの管理などしたくはないのですが、しないといけないケースもあるでしょう。最低限やらないといけないこととしては、ソルト生成した上でハッシュ化したパスワードを保存することではないでしょうか。.NET Coreでこれらの処理を行う必要があった時に調べたコードをまとめます。

まずソルトを作成する場合、.NET CoreではRNGCryptoServiceProviderクラスを使って暗号論的に乱数を生成します。GetBytesメソッドは、引数に指定したbyte配列を、生成した乱数で埋めます。

docs.microsoft.com

次にパスワードのハッシュ化ですが、PBKDF2を利用する場合、Rfc2898DeriveBytesクラスを利用できます。ハッシュ化対象の文字列、ソルト、反復回数、ハッシュアルゴリズム名を指定できます。ハッシュアルゴリズムはデフォルトがSHA1なので適宜変更しましょう。

docs.microsoft.com

以上をまとめてコードにするとこうなります。hashedPasswordはbyte配列なので適宜保存しましょう。

using System.Collections.Generic;
using System.Security.Cryptography;

var password = "<入力されたパスワード>";
//必要な長さ分の配列を先に用意する
var salt = new byte[1024];
//RNGCryptoServiceProviderはIDisposableを実装しているので適宜破棄する
using var rng = new RNGCryptoServiceProvider()
rng.GetBytes(salt);

var b = new Rfc2898DeriveBytes(password, salt, 100, HashAlgorithmName.SHA256);
var hashedPassword = b.GetBytes(256);

ソルトとハッシュ値を保存しておき、入力されたパスワードと照合する場合は、保存したソルトを使って、同じ反復回数、ハッシュアルゴリズムでハッシュ化し、両者を比べます。byte配列なのでSequenceEqualメソッドを使っています。

using System.Collections.Generic;
using System.Security.Cryptography;
using System.Linq;

var password = "<入力されたパスワード>";
var salt = "<保存していたソルト>";
var hashedPassword = "<保存していたハッシュ値>";
var b = new Rfc2898DeriveBytes(password, salt, 100, HashAlgorithmName.SHA256);
var challengePassword = b.GetBytes(256);
if (!(hashedPassword?.SequenceEqual(challengePassword) == true))
{
    throw new Exception("ログイン失敗");
}