Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9571

Base64 encode を C# で実装してみる

$
0
0

Base64 の目的

8bitで表現される任意の値が設定されたバイト列を、安全に通信するためにテキストで表現できる形式に変換すること。

Base64 encode の変換形式

8bitのバイト列を6bitずつ取り出し、[a-zA-Z0-9+/] に割り当て直す。

つまり、24 bit = 8 bit * 3 のバイト列は 24 bit = 6 bit * 4 のバイト列に変換される

変換の概要

8bit の列を 6bit の列に分割する

bbbbbbbb bbbbbbbb bbbbbbbb
↓
bbbbbb bbbbbb bbbbbb bbbbbb

6bit の列を定義した変換表を使って変換する

0b000001 = 'A'
0b000010 = 'B'
0b000011 = 'C'
...
0b111111 = '/'

3バイトに満たない列には'='を埋める

Base64 の問題点

割り当て直す [a-zA-Z0-9+/] のうち、[a-zA-Z0-9] はどういう環境でも安全に扱えるが、のこりの [+/] については環境によってはそのまま使えず別途エンコードする必要がある。たとえば '/' はパス区切りには使えず、URL表現だと問題が発生するし、'+' も同様にURL表現で問題が発生する可能性がある。

ゆえに[+/]を別の安全に使えそうな文字に置き換えるとかする必要がある(典型的にはURLEncodeによって数値参照形式にするなど)

C#による実装

似たロジックはSMF(Standard MiIDI Format)における8bitの列から7bitずつを取り出す方法だが、SMFだとC#のバイト列とはエンディアンが逆なのでそのままは使えない。

参考: https://qiita.com/ringorou/items/5e2384f7cf226d9e648a

考え方

変換対象のバイト列の長さは不定だが、8bit から 6bit ずつ取り出せばよいので 24bit のみ考えればよい。

1.第1バイトを2ビット右シフトした値と 0b111111 の積 a を格納
2. 第1バイトを4ビット左シフトした値と 0b111111 の積 b を格納
3. 第2バイトを4ビット右シフトした値とbの和を 0b111111 の積をとった値 c を格納
4. 第2バイトを2ビット左シフトした値と 0b111111 の積を d を格納
5. 第3バイトを6ビット右シフトした値とdの和を 0b111111 の積をとった値 e を格納
6. 第3バイトと 0b111111の積 f を格納
7. 格納した値 a, c, e, f の列を変換表を使って変換する
8. 余った位置には'=' を追加する

実装

// 変換表staticreadonlystringmap="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";publicstaticstringEncode(byte[]source){// エンコード後の長さを計算し、余る長さを求めるvarencodedLength=source.Length*8/6;varremain=0;if((source.Length*8%6)>0){encodedLength++;remain=(4-encodedLength%4);encodedLength+=remain;}varencoded=newchar[encodedLength];intoffset=0;bytenext=0;varn=0;varm=0;inti;// 変換の本体while(n<source.Length){next=0;for(i=0;i<3&&n<source.Length;++i,++n){offset=(i+1)*2;encoded[m++]=map[(next|(source[n]>>offset))&0b111111];next=(byte)((source[n]<<(6-offset))&0b111111);}encoded[m++]=map[next];}if(remain>0){for(varp=encodedLength-remain;p<encodedLength;++p){encoded[p]='=';}}returnnewstring(encoded);}

その他

  • 当然、組み込みの関数を使った方が効率はよい。
  • 例えば '+','/' を別の文字に置き換える場合でも組み込み関数の出力を Replace等で置き換えた方が早い。
  • 上のロジックでnew string()するのはいかがなもの、と思うがStringBuilderに変換結果を入れても最終的には同じことになるので意味はない
  • 他に高速化、効率化するポイントはあると思うけどとりあえずの目的は達成できる

以下はそれぞれ Convert.ToBase64String, それの'+/'をReplaceで変換するもの、この記事のロジックでのベンチマーク結果。

MethodMeanErrorStdDevGen 0Gen 1Gen 2Allocated
ConvertBase64249.4 ns560.1 ns30.70 ns0.0758--120 B
ConverBase64Replace310.4 ns404.5 ns22.17 ns0.1049--165 B
OriginalBase64543.8 ns982.8 ns53.87 ns0.1216--192 B

Viewing all articles
Browse latest Browse all 9571

Trending Articles