ぺぷしのーげん

アプリケーションエンジニアによる雑記ブログ

C#と.NETを使ってJsonWebTokenをRS256で署名して作成する方法

f:id:hazakurakeita:20160619120834p:plain

JSONのHTTPによるセキュアなやり取りにJson Web Token(JWT)という規格があるそうです。今回、Windowsタブレットのアプリとサーバーとのやり取りをJSONとJWTを使ってやろうという話になったのですが、ちょっと待って、そんな規格知らないし、たぶん.NETにそんなライブラリないぞ…。

 

Json Web Tokenとは

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

  • ヘッダー
  • JSON
  • 署名

ヘッダー

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

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

JSON

{
 "title": "ぺぷしのーげん",
 "auther": "Keita",
 "url": "http://hazakurakeita.hatenablog.com/"
}

送信するJSONです。説明不要でしょう。

署名

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0aXRsZSI6IuOBuuOBt-OBl-
OBruODvOOBkuOCkyIsImF1dGhlciI6IktlaXRhIiwidXJsIjoiaHR0cDovL2hhe
mFrdXJha2VpdGEuaGF0ZW5hYmxvZy5jb20vIn0

ヘッダーとJSONをバイト文字に変換し、ピリオドを区切り文字として繋げます。これを暗号化したものが署名となります。

JWTの全体

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0aXRsZSI6IuOBuuOBt-OBl-
OBruODvOOBkuOCkyIsImF1dGhlciI6IktlaXRhIiwidXJsIjoiaHR0cDovL2hhe
mFrdXJha2VpdGEuaGF0ZW5hYmxvZy5jb20vIn0
.Lv8fQQ1r_LDjsnGLde88
vhU1mNlPYZokg-pl9LGavJcGcZN9Ayzw-pnEf56fKCrZ1pA6E-
dfwQBZa6_sFo5mpble3D54yeNzPfhr4QxOpsgXsM56My5ui3kGOo5YX9qrj
kAw6DH-P2WU8_tuA1Gem80xWKDZHW6m_xXL2E9o5Pw

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

 

.NETにライブラリはあるのか?

なんかMSDNにそれっぽいライブラリの記載は確認できます。しかし、MSDNに使い方の説明が全くありません。プロパティ名やメソッド名が載っているだけです。英文表示してみ同じです。ググってみても他サイトの記事は全くヒットしません。英語圏を含め、使用している人を見つけることはできませんでした。プロパティ名やメソッド名を見ても使い方が想像できないので、今回はこの使用を断念しました…。

 

自力でJWTを作る

RubyなどWEBの世界では普及してる?ようでライブラリが豊富にあるようです。一方で.NETやC#向けのライブラリは少なく、情報もなかったので自力でJSONをJWTにすることに。手順としては以下の通り。

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

全体のソースコード

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

stackoverflow.com

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

暗号化を1から考える

しょうがないので暗号化を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を指示してあげるだけで署名してくれます。ただ、注意点があります。

秘密鍵でしか暗号化できない

SignDataメソッドは秘密鍵でしか暗号化できません。FromXmlStringメソッドで公開鍵を渡していると、SignDataメソッドはCryptographicExceptionをスローします。JWTの仕様では署名を秘密鍵、暗号鍵どちらを使うか決まっていません。どちらを使うかは暗号鍵方式の仕様に従いましょう。送信者が鍵を作るなら秘密鍵、受信者が鍵を作るなら公開鍵で暗号化しましょう。

では公開鍵で暗号化するにはどうすればいいのでしょうか。今のところ不明です笑。Encryptメソッドを使えば良いようですが、このメソッドだとSHA256を指定する方法がありません。ちなみにEncryptメソッドは秘密鍵を使おうとするとCryptographicExceptionをスローします。

 

処理そのものはシンプルなのですが、いかんせんJWTの仕様がどこにも書かれていないので苦戦しました。サーバー側はライブラリ使っていたので、そこで認証できればOKという感じでしたね。まあ、とりあえずできて良かったです。

 

おしまい。