Karakuri.com

Fintechではたらくアプリケーションエンジニアの技術録

C#と.NET FrameworkのみでJsonWebTokenを使ったJson署名を実現する方法

JSONのHTTPSによるやり取りにJson Web Token(JWT)という規格があります。JSONに署名して中間者攻撃や悪意ある送信元からのAPI利用を防ぐことができます。しかし当然ながら.NET Frameworkにはそのようなライブラリは存在しません。Nugetにも該当するライブラリを見つけることができなかったので、自作してみました。

Json Web Tokenとは

Json Web Tokenとは送信者がJSONに署名し、受信者が署名を確認して送信者と偽装のないことを確認するための規格です。略してJWTと呼ばれます。このJWTは以下の3つの要素からなる文字列として表現されます。

  • ヘッダー
  • JSON
  • 署名

ヘッダーは下記のようになります。署名の暗号アルゴリズムとJWTであることを明記します。暗号アルゴリズムは色々あるようですが、今回はRS256という形式を使いました。これは公開鍵暗号方式でハッシュアルゴリズムにSHA256を使うという意味です。

{
 "alg": "RS256",
 "typ": "JWT"
}

署名はヘッダーとJSONをバイト文字に変換し、ピリオドを区切り文字として繋げます。これを暗号化したものが署名となります。3つの要素はピリオドで区切られており、また署名は送信者により暗号化されています。受信者は署名を復号化し、JSONと比較することで偽装ないことを確認します。ここで一致しなければ、偽装されているか、送信者が違うということになります。

自力でJWTを作る

RubyなどWEBの世界では普及してる?ようでライブラリが豊富にあるようです。一方で.NETやC#向けのライブラリは少ないです。手順としては以下の通り。

  1. ヘッダーをバイト文字に変換
  2. JSONをバイト文字に変換
  3. ヘッダーとJSONをピリオドでつなぎ、暗号化
  4. ヘッダー、JSON、暗号化した署名をピリオドでつなぎ完成

全体のソースコード

実は、ソースコードはここに投稿されている内容でほぼカバーできます。
stackoverflow.com

しかし、このソースコードには重大な欠陥があります。署名の暗号化を行っていないので代わりに署名をSHA256でハッシュ化しており、受信者がヘッダーとJSONをハッシュ化して署名と比較するという手法を取っています。これは受信者もC#で開発していることが前提で、JWTの仕様とは異なるような気がしますね。あくまで署名を暗号化し、受信者が署名を復号化して比較するというのがJWTです。

しょうがないので暗号化を1から考えます。ここでRS256という暗号アルゴリズムの仕様を知りたくなるのですが、残念ながら情報は皆無。どこかのブログで英語でSHA256を含むRSA暗号鍵を利用した方法と1行で書かれていただけです。そこから導き出した答えがこれ。

var rsa = new RSACryptoServiceProvider();

rsa.FromXmlString(privateKey);

var sign = rsa.SignData(Encoding.UTF8.GetBytes(data), "SHA256");

これだけで暗号化された署名が作れます。dataはヘッダーとJSONをピリオドで繋いだバイト文字列です。RSACryptoServiceProviderクラスのSignDataメソッドにSHA256を指示してあげるだけで署名してくれます。処理そのものはシンプルなのですが、いかんせんJWTの仕様がどこにも書かれていないので苦戦しました。サーバー側はライブラリ使っていたので、そこで認証できればOKという感じでしたね。まあ、とりあえずできて良かったです。