任意のバイナリを安全な文字で表す

base64URIのパーセントエンコーディングなどに共通のアイデアが、「任意のバイナリを安全な文字で表す」ことである。

モチベーション

テキストエディタで画像などのバイナリファイルを開くとひどい表示になる。
これは、テキストエディタがファイルのバイト列を何らかの文字コードで解釈しようとして失敗しているためである。いわゆる文字化けのようなもの。

あらゆるバイト列をascii印字可能文字で表現できれば嬉しいかもしれない。
そうすれば文字化けは起きないし、クリップボードに保存できるし、例えば「HTMLにテキストで画像を埋め込む」という応用などができる。

バイト列とascii印字可能文字の相互変換の方式として様々なものが考えられている。
ここからは、バイト列をascii印字可能文字に変換することを「エンコード」と呼び、バイト列とascii印字可能文字に変換するルールを「エンコード方式」と呼ぶことにする。

冗長度

https://ja.wikipedia.org/wiki/ASCII
asciiの印字可能文字はアルファベット52種、数字10種、記号33種の計95種しかない。これは1バイト(=8ビット)での01の組み合わせ256通りを表現するには不十分である。
なので、1バイトを表現するために、ascii印字可能文字からなる2文字以上の文字列で表現せざるをえない。

一方、asciiコードのテキストファイルを保存すると、1文字が1バイトで保存される。
なので、元のバイト列をそのままファイルに保存するよりも、エンコードして保存するほうがサイズが大きくなる。
冗長度として、(エンコードしたときの文字数)/(元のバイト数)というものを定義できる。
エンコード方式としては、冗長度が小さい方が望ましい。

文字の安全度

個人的な感覚として、ascii印字可能文字の中でも、比較的安全度が高い文字と低い文字がある。(左ほど安全度が高い)
【数字・アルファベット】 > 【-_+.】 > 【!?#$%&@~^*:;=】 > 【()<>[]{}'"`|/,】 > 【\】 > 【半角スペース】

安全度の基準としては「shellのコマンドラインで特殊な意味を持つかどうか」「テキストベースのデータ表現方法(XMLなど)で特別な意味を持つかどうか」くらいの感覚である。

安全度の低い文字はできればエンコードに使いたくない。
例えば、HTMLにテキストで画像を埋め込むときに < > " (半角スペース) があるとエラーが起きそうで嫌である。
エンコード方式としては、安全度の高い文字のみを使う方が望ましい。

エンコード方式例

16進数

代表的なエンコード方式としては16進数がある。
これは0123456789abcdefの16文字を4ビットに割り当て、2文字で1バイトを表す方式で、バイナリとの相性が良い。

1バイトを表すのに2文字必要なので、冗長度は2になる。

バイナリとの相性が良いので、バイナリエディタの表示方式としても使われる。
http://forest.watch.impress.co.jp/library/software/binaryeditbz/
sha1のようなハッシュ値でも使われる。
http://ftp.riken.jp/Linux/centos/7/isos/x86_64/sha1sum.txt

2進数・4進数・8進数・32進数

2進数: バイト列をそのまま全て01で表示する方式。
1バイトを表現するのに8文字使うため、冗長度が8と非常に冗長。
デジタルであることを表すかっこいいイメージに使われる。
google:image:digital
あとは学習用くらいだろうか。

4進数: 0123の4文字を2ビットに割り当てる。
冗長度は8/2=4

8進数: 01234567の8文字を3ビットに割り当てる。
冗長度は8/3=2.666..

32進数:123456789abcdefghijklmnopqrstuvの32文字を5ビットに割り当てる。
冗長度は8/5=1.6
ポケモン個体値で見ることがある。

64進数 (base64)

https://ja.wikipedia.org/wiki/Base64
アルファベット・数字A~Za~z0~9に記号+/を加えた64文字を使って6ビットを表現する。冗長度は8/6=1.333...
また、パディング用の文字として=も使う。

記号+/を含むのが厄介で、例えばURLに/は使いたくない。
そのような問題を回避するため、記号部分を変更したbase64変種がいくつか提案されている。
例えばURLのためのbase64は、記号に+/ではなく-_を使い、さらにパディングがない。

パーセントエンコーディング

URLにおいてマルチバイト文字や記号を扱うための方法
パーセントエンコーディング - Wikipedia

基本は16進数で、%とその後の2文字(0123456789ABCDEFの16種)で1バイトを表す。
ただし、アルファベット・数字・一部のascii記号はエンコードしないまま表示できる。
パーセント(%)自身も%25でエンコードしなければならない。

冗長度は、アルファベットや数字のところでは1、それ以外のところでは3になる。

quoted-printable

https://ja.wikipedia.org/wiki/Quoted-printable
パーセントエンコーディングとほぼ同じ。
=とその後の2文字(0123456789ABCDEFの16種)で1バイトを表す。
ただし、アルファベット・数字・ascii印字可能文字はエンコードしないまま表示できる。
=自身はエンコードしなければならない。

メールで用いられることがある。

unsigned charの配列

0~255の数字の配列として書いてもいいかもしれない。

  • 例:
    • "[227,129,147,227,130,147,227,129,171,227,129,161,227,129,175]"
    • utf8の「こんにちは」をJSONの数字配列として解釈可能な文字列にしている

冗長度はおよそ3.6

JavascriptのUint8Arrayで読み込める。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array

用途

これまで書いてきたものも含む。

HTMLにテキストで画像を埋め込む

http://dev.classmethod.jp/ria/image-performance-tuning-03/

7bit文字しか認識しないメール経路でも安全に日本語メールや添付ファイルを送る

http://ascii.jp/elem/000/000/588/588971/

テキストベースのデータ構造で、エスケープを気にせず扱う

例えばtsvを作成するとき、データにタブ文字や改行があると、単にタブでjoinするだけでは正しいtsvにならず、tsv作成用のライブラリを使って適切にエスケープする必要がある。
読み込むときも同様で、タブでsplitするだけでは駄目で、tsv読み込み用のライブラリを使わなければならない。

「データは全てbase64に変換する」というルールを適用すると、データにタブ文字も改行も入らなくなり、タブでjoin・splitするだけでtsvの作成・読み込みができるようになる。
shellでsort,cutを行うときにも安心して行うことができる。

ただし、変換せずテキストとして読むことは不可能になるし、毎回base64エンコード・デコードを気にするよりtsvライブラリを使うほうがマシであることが多い。
一応こんな選択肢もあるよ、くらいの認識にしておきたい。