【C#】Base64urlエンコード・デコード

# はじめに
Gmail APIを触る際にBase64urlのエンコード・デコードを行う必要があったため、C#(.Net Framework)でBase64urlのエンコード・デコードについて調べました。

# 動作環境
Visual Studio 2015 Community
.NET Framework 4.5.2

# Base64urlとは
Base64urlは64種類の英数字を用いた形式であるBase64エンコード方式に対してURLが含まれていても問題がないように(URL safe)、以下の変換を行うエンコード方式です。RFC4648にて定められています。
 * 「+」を「-」に置き換える。
 * 「/」を「_」に置き換える。
 * パディング(=)を入れない(ただし、暗黙的にデータ長がわかっている場合に可能)

参考：
https://en.wikipedia.org/wiki/Base64
https://tools.ietf.org/html/rfc4648#section-5

パディングは規格等によりますが、入れないとしている場合が多いようです。

参考：
https://tools.ietf.org/html/rfc6920#section-3
https://tools.ietf.org/html/rfc7636#section-3
https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-40#section-2

# 実装
パディングを入れない場合で実装してみます。

## .Net Frameworkのみで実装する場合
.Net Frameworkにbase64エンコード・デコードするメソッドが存在するので、エンコード後及びデコード前にbase64urlの対応を行う事でbase64urlエンコード・デコードを行うことができます。

### サンプルコード

```csharp
using System;
using System.Text;

namespace Base64url_Sample
{
	class Program
	{
		static void Main( string[] args )
		{
			string target = "Base64url 文字列変換サンプル";

			// UTF8 ⇒ Base64url
			string base64UrlStr = Convert.ToBase64String( Encoding.UTF8.GetBytes( target ) )
									.TrimEnd( '=' )             // パディングを削除
									.Replace( '+', '-' )        //「+」⇒「-」
									.Replace( '/', '_' );       //「/」⇒「_」

			Console.WriteLine( base64UrlStr );

			// Base64url ⇒ UTF8
			int paddingNum = base64UrlStr.Length % 4;
			if( paddingNum != 0 ) {
				paddingNum = 4 - paddingNum;
			}
			string utf8Str = Encoding.UTF8.GetString( Convert.FromBase64String(
								( base64UrlStr + new string( '=', paddingNum ) )    // パディングを追加
								.Replace( '-', '+' )                                //「-」⇒「+」
								.Replace( '_', '/' ) ) );                           //「_」⇒「/」

			Console.WriteLine( utf8Str );
			Console.ReadKey();				//キー入力までウィンドウが閉じないようにする
		}
	}
}

```

上記サンプルコードを実行すると以下の出力結果となります。

![base64url_1.png](https://qiita-image-store.s3.amazonaws.com/0/86057/4f0dbcbb-d3f4-e918-c378-b7bda4af6266.png)


### 解説
エンコード・デコードについての解説です。

#### エンコード
現在の文字コード(サンプルコードではUTF-8)に合わせてデコードしたbyte配列をConvert.ToBase64Stringメソッドに渡すことでbase64にエンコードします。更にbase64urlに変換するため「+」を「-」に、「/」を「_」に置換します。また、base64に変換した直後はパディング(=)が存在するため、パディングを除去します。

参考：
https://msdn.microsoft.com/ja-jp/library/dhx0d524(v=vs.110).aspx


#### デコード
エンコードで行ったことの逆処理を行います。
「-」を「+」に、「_」を「/」に置換します。また、文字列長が4の倍数となるようにパディング(=)を追加します。その後変換した文字列をConvert.FromBase64Stringメソッドに渡す事でデコードされるので、任意の文字コード(サンプルコードではUTF-8)にエンコードします。

参考：
https://www.facebook.com/notes/たまておどり本舗/cでfacebookページのいいね判定をおこなう方法-base64url-decodehmac-sha-256/204921202914775/
https://msdn.microsoft.com/ja-jp/library/system.convert.frombase64string(v=vs.110).aspx

## OWINのBase64urlエンコード・デコードを使用する場合
Microsoft.Owin.SecurityにBase64urlエンコード・デコードを行うメソッドがあるのでこれらを使用することでもbase64urlエンコード・デコードを行うことができます。ライセンスは[MICROSOFT SOFTWARE LICENSE TERMS](https://www.microsoft.com/web/webpi/eula/net_library_eula_enu.htm)です。

参考：
https://msdn.microsoft.com/en-us/library/microsoft.owin.security.datahandler.encoder.textencodings.base64url(v=vs.113).aspx

あらかじめ、パッケージマネージャーで`Install-Package Microsoft.Owin.Security`を実行して[nuget](https://www.nuget.org/packages/Microsoft.Owin.Security/)からパッケージを取得する必要があります。パッケージマネージャはメニューから[ツール]⇒[NuGetパッケージマネージャー]⇒[パッケージマネージャーコンソール]で表示されます。

### サンプルコード

```csharp
using System;
using System.Text;
using Microsoft.Owin.Security.DataHandler.Encoder;

namespace Sample_Base64url
{
	class Program
	{
		static void Main( string[] args )
		{
			string target = "Base64url 文字列変換サンプル";
			var base64UrlEncoder = new Base64UrlTextEncoder();

			// UTF8 ⇒ Base64url
			string base64UrlStr = base64UrlEncoder.Encode( Encoding.UTF8.GetBytes( target ) );

			Console.WriteLine( base64UrlStr );

			// Base64url ⇒ UTF8
			string utf8Str = Encoding.UTF8.GetString( base64UrlEncoder.Decode( base64UrlStr ) );

			Console.WriteLine( utf8Str );
			Console.ReadKey();				//キー入力までウィンドウが閉じないようにする
		}
	}
}
```

上記サンプルコードを実行すると最初のサンプルコードと同じように以下の出力結果となります。

![base64url_2.png](https://qiita-image-store.s3.amazonaws.com/0/86057/0ba48c9a-7b37-a644-fcb7-22064dc67214.png)


### 解説
エンコード・デコードについての解説です。

#### エンコード
現在の文字コード(サンプルコードではUTF-8)に合わせてデコードしたbyte配列をBase64UrlTextEncoderクラスのインスタンスのEncodeメソッドに渡すことでエンコードします。

#### デコード
Base64UrlTextEncoderクラスのインスタンスのDecodeメソッドに文字列を渡すことでデコードされるので、任意の文字コード(サンプルコードではUTF-8)にエンコードします。

# おわりに
C#でBase64urlエンコード・デコードについて記載しました。

RFC4648の記載が条件付きでパディング外すというのが厄介ですね。
それために言語サポートする場合に議論が起きているみたいです。
パディング必須な例が見つからなかったのでなんとも言えませんが。

https://github.com/golang/go/issues/4237
https://bugs.ruby-lang.org/issues/10740

また、パディング無しがメジャーなみたいなのでパディングについて記載が無い場合にBase64url変換してますという記載がある場合はパディング無しと見なした方がいいかもしれません(Gmail APIもパディング無しでした)。