2016年2月13日土曜日

base64の仕組みとエンコード・デコード

mainpic

webの仕事だとbase64のデータを扱うことが割と多いかと思います。

とはいえ、ライブラリがあったりframework側が勝手にencode, decodeしてくれるモノが多いので、気にする機会は少ないです。

なので今回はbase64の仕組みとエンコード・デコードに関して記事を書いていきます。

Base64とは

Base64 -wikipedia-によると

データを64種類の印字可能な英数字のみを用いて、それ以外の文字を扱うことが出来ない通信環境にてマルチバイト文字やバイナリデータを扱うためのエンコード方式である。

となっています。

使われるのは画像データをサーバーに送信したり、cssに埋め込んでアイコンとして利用したりします。圧縮技術ではないので、データ量は小さくなるわけではなく、少々増えることになります。(133%程度らしい)

エンコード・デコード形式

可逆変換なので、エンコードの逆を行えばデコードになります。

エンコードの手順は以下の通りです。

  1. inputのデータを6bitに分割
  2. 不足分は0を足して6bitになるようにする
  3. 6bitの値を変換表を元に4文字ずつ印字可能な英数字に変換
  4. 4文字に満たない分は =を追加して4文字にする

ということになります。

エンコード対象の文字列を「Hello World!!」として変換していきます。

  1. 「Hello World!!」を一文字ずつ分割して、ascii文字コードから1byteをbit表示にする。

    [H] → 01001000
    [e] → 01100101
    [l] → 01101100
    [l] → 01101100
    [o] → 01101111
    [ ] → 00100000
    [W] → 01010111
    [o] → 01101111
    [r] → 01110010
    [l] → 01101100
    [d] → 01100100
    [!] → 00100001
    [!] → 00100001
  2. 全て連結して、6bitずつに分割し、変換表に合わせて文字列に英数字に置き換える。

    010010 → [S]
    000110 → [G]
    010101 → [V]
    101100 → [s]
    011011 → [b]
    000110 → [G]
    111100 → [8]
    100000 → [g]
    010101 → [V]
    110110 → [2]
    111101 → [9]
    110010 → [y]
    011011 → [b]
    000110 → [G]
    010000 → [Q]
    100001 → [h]
    001000 → [I]
    010000 → [Q]
  3. 4文字ずつ分割して、足りない部分に=を足す。

    SGVs bG8g V29y bGQh IQ
    ↓
    SGVs bG8g V29y bGQh IQ==

完成です!

javascriptで書いてみた

30分くらいで計算量や時間を気にせず書きました。
実際に組み込むときにはもう少し考えた方が良いかと思いますが。

'use strict'
var dic = [
  'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
  'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
  'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
  'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
];

function toBase64(binary) {
  var mid = "";
  for (var i = 0; i < binary.length; i++) {
    var c = binary.charCodeAt(i).toString(16); // to 16進数
    c = parseInt(c, 16).toString(2); // to 2進数
    for(var j = c.length; j < 8; j++) c = '0' + c;
    mid += c;
  }
  var base64 = "";
  for (var i = 0; i < mid.length; i = i + 6) {
    var sliced = mid.slice(i, i+6);
    for(var j = sliced.length; j < 6; j++) sliced += '0';
    var b64 = dic[parseInt(sliced, 2)]; // to 10進数
    base64 += b64;
  }
  var mod = base64.length % 4;
  base64 += mod != 0 ? Array(mod + 1).join('=') : '';
  return base64;
}

var str = "ABCDEFG";
console.log("-- begin --");
console.log("input string: '%s'", str);
console.log(toBase64(str));

URLクエリパラメータとの関係

httpのGETを利用する場合などにURLのクエリパラメータに付与したいことがあります。
http://hogehoge?q=base64string….のbase64stringの部分です)

base64には英数字以外の文字+/=の三つが使われています。
これらをURLのクエリパラメータに入れると別な意味合いとなってしまうので注意が必要です。

+ : クエリ文字列のスペース(?q=v1+v2)
/ : pathの区切り文字列
= : クエリ文字列のkey, valueをつなぐ等号

そこで(実装次第ですが)例えば+-/_=→削除というように変換してクエリパラメータに付与する。

サーバー側では逆に-+_/、4で割ったあまりに対して=を足す。

という形で逆変換すればデータが復活します。

javascriptで書いてみた

function encodeURL(str){
    return str.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=+$/, '');
}

function decodeUrl(str){
    str = (str + '===').slice(0, str.length + (str.length % 4));
    return str.replace(/-/g, '+').replace(/_/g, '/');
}

こういった形で変換と逆変換すれば、クエリパラメータでもbase64を利用することが出来ます。
とはいえ、GETのクエリパラメータには文字長の制限があるので長すぎる場合には注意してください。


今日はbase64で遊んでみました。

上述したtoBase64の関数に関しては計算量がO(n^2)なのでどうにかならないかなとか思っていますが、暇な時にはどこかのライブラリを眺めてみると面白いかもしれないです。

では、良いインプットと良いプログラミングを。

0 件のコメント:

コメントを投稿