文字コードとかbase64とかハッシュ値とかいろいろ調べてみた

仕事上、エンコードやらデコードやらをいじる機会がありこの辺まったく知らないでいたのでいくつかのページを調べたまとめ。

# 文字コード

## 用語整理

### 文字列

文字列（もじれつ）は、単語や文章のような、文字の連なったもの。ストリング (string)、テキスト (text) という場合もある。コンピュータ、特にプログラミングの分野で用いることが多い。 (from Wikipedia先生)

### シーケンス

複数の値を順番に並べたものをひとかたまりにしたもの。pythonでは、シーケンス型と呼ばれる型(総称)があり、文字列のほかリスト(配列)やタプルもシーケンスに該当する。
要するに同じものがたくさん並んでいるもののこと。

## 文字コード

コンピュータの世界は全て2進数。すなわち0か1でしか物事を言い表せない。
全ての情報は0か1かのビットの並び(ビット列)として表現しなければならないのだ。

世の中の文字が0か1かの2種類しかないかというとそんなことはなくて、すでにこの記事上だけでも100種類くらいの文字が存在しているだろう。
では、この何種類もの文字を0と1だけで表さなければならないとしたらどうするか？

0と1をいくつも組み合わせて文字を表現することが思いつくだろう。そしてその表現にはルール・規則が必要になる。なんの文字をどう01で表すのか。
文字コードとは、この01の配列と文字の変換規則のことである。

そして、これまでに人類は様々な変換規則をつくってきた。

* ASCIIコード
* ISO8859
* Unicode
* JIS

etc...

例えばASCIIコードは各文字に7ビット(0か1の7桁)の数字が対応したものであり、その対応規則を元に文字とビット列を変換している。
例をあげると、コンピュータの内部ではこんな感じで文字が保存されたり、送られたりする。

`1110001 1101001 1101001 1110100 1100001`

人からするとこのままでは何を言ってるのか全くわからないので、コンピュータの内部でASCIIコード表(とは限らないがここではASCIIとする)を参照し、`qiita`という文字列にして画面に出してあげる。

このASCIIコードは本当は1文字あたりに8ビット当てられており、最後の1つは誤り訂正のためのチェックビットであった。しかし、通信技術の進歩とともにそのチェックは不要と判断され、2^8種類の文字を表現できるようになった。そこで生まれたのがISO8859である。

最後の1ビットの解放により、拡張された部分は各国で独自の文字が当てられている(ISO8859には様々なサブタイプがあり、キリル文字が入っているのがISO8859-5、ギリシャ文字が入っているのはISO8859-7のようになっている)。

そしてもちろんこのISO-8859規格に日本語はない。
ひらがな、カタカナ、漢字、、全て合わせたらASCIIコードを上書きしても収まりきらないだろう。つまり、日本語は1バイトでは表せないのである。  

## Unicode UTF-8 UTF-16

### Unicode

同じ数値が違う文字をさしていたり、日本語のように多種の文字がありすぎて2^8に収らないとなると何かと不便が多い。
そこで、Unicodeという、全ての文字に固有の名前とIDを付与して管理しようとする変換規則が生まれた（正確にはまだ作っている途中っぽい）

Unicodeは、`\u`の後ろに16進数を4つ繋げた4バイトで表される文字であり、基本多言語面と呼ばれ通常使用されるほとんどの文字が含まれている。
それ以外の文字（追加多言語面や追加漢字面）は`\U`の後ろに16進数を8個繋げた形式で表される。また、`\N`の後ろに`{}`付きでその文字の名前を表す。

pythonにはunicodedataモジュールがあり、この中のlookup関数を用いると、`\N`から始まるUnicode名からUnicode文字を取得でき、逆にname関数を用いると、指定した文字の名前が返される。

### UTF-8 UTF-16

これらは、文字から符号(ビット列)に変換する方式を定めたものである。エンコードやデコードの方式のこと。
上述の通り、Unicode(基本多言語面)は必ず4バイト分のIDが充てられている。しかし、一般的なASCIIにあるような文字を使うのにわざわざ4バイトもメモリを使用するのは勿体無い。

そこでUTF-8では符号を8bit単位で表し、UTF-16では16bit単位で表す動的エンコード方式とすることで、必要ない部分を削り落としメモリの無駄を省いている。具体的には以下のようなルールで振り分けている。

|16進|2進|
|:---|:---|
|00000000〜0000007F|0xxxxxxx|
|00000080〜000007FF|110xxxxx 10xxxxxx|
|00000800〜0000FFFF|1110xxxx 10xxxxxx 10xxxxxx|
|00010000〜0010FFFF|11110xxx 10xxxxxx 10xxxxxx 10xxxxxx|

よく使われるASCIIコードほど使用するバイト数が抑えられることになる。

例えば「A」を例にとると以下のようになる。

|||
|:---:|:---|
|Unicode|\u00000041|
|Unicode(UTF-16)|0x0041|
|Unicode(UTF-8)|0x41|
|ASCII|41|


UTF-8で表すことで余計にメモリを使わなくて良いほか、ASCIIコードと互換性を手にすることができた。
ただ必ずしもメモリ消費が少ないかというとそうでもない。
同じく「あ」と例にとると次のようになる。

|||
|:---:|:---|
|Unicode|\u00003042|
|Unicode(UTF-16)|0x3042|
|Unicode(UTF-8)|0xE3 0x81 0x82|
|ASCII|-|

UTF-16では変わらず2バイトだが、UTF-8では3バイト使用しないと表せなくなった。

## Shift-JIS

UTF-8などと同じく符号化の方式であり、文字集合はJISである。
漢字全てを表すには当然8bitでは足りない。

そこで、2バイト(16bit)を使用することで理論上2^16種類の文字を表せる。
https://www.google.com/search?q=shiftJIS&rlz=1C5CHFA_enJP779JP780&oq=shiftJIS&aqs=chrome..69i57j0l7.2823j0j7&sourceid=chrome&ie=UTF-8

## pythonによるエンコード、デコード

### エンコード(ASCII・UTF-8)

文字列 → バイト列(ビット列)に変換する作業。
encode関数は第一引数に変換方式（エンコーディング名）を指定する。
`ascii`は古典的な7ビットのビット列への変換を指定し、`utf-8`は上記の動的エンコーディングを指定する（一般的）。
なお、`unicode-escape`とすると、これまでの`\u`や`\U`を付した状態のリテラルが返される。

```
>>> "あ".encode('utf-8')
\xE3\x81\x82
```

pythonで表示すると0xのゼロは消えるらしい。
また、encode関数には第二引数を指定することができ、第一引数のエンコーディングができない場合の対応を設定できる。

### デコード(ASCII・UTF-8)

バイト列 → 文字列に変換する作業。
エンコードと同様に変換方式を指定する。逆を実施しているだけなので省略。

# base64

じゃあbase64は何か？
これは同じくエンコードの方式ではあるが、その対象が文字の話ではなく画像などデータ一般の変換の話。

昔はメール送受信のプロトコルSMTPではASCIIコードに含まれる英数字と一部に記号(+と/)のみしか対応していないために画像や音声などのデータを送る時に対応できなかった。
その後、MIME(Multipurpose Internet Mail Extensions)という規格が作られ、全てのデータを英数字で表すbase64という変換方式が定められたことで、これらのデータを送受信可能になった。
この背景から、base64エンコーディングはASCIIの英数字と上記の記号のみを用いた変換方式の事で、その用途はビット列と文字の変換ではない。

詳細はこの記事が勉強になった。
https://qiita.com/PlanetMeron/items/2905e2d0aa7fe46a36d4

pythonのbase64モジュールが提供するエンコードとデコードの関数は以下。

* b64encode()
* b64decode()

## b64encode()

引数は bytes-like object。
bytes-like object ? → おそらくbytes型のこと。つまりバイト列(8bit整数の列)

→上述のASCIIコードの一部の文字に変換される。

## b64decode()

引数は bytes-like object または ASCII。
引数にASCIIをとるのでb64encodeで生成したASCIIをそのままパラメータに渡して戻すこともできる。

# ハッシュ

ハッシュ化はこれまでのように文字や画像をバイト列へ変換する規則を定めたものではない。これまでは双方向に変換可能であるでがハッシュ化は元に戻すことのできない難読化を指す。

pythonではhashlibモジュールを用いる。

ハッシュ化にはsha256やmd5など種々の方法がある。
sha256()はSHA-256ハッシュオブジェクトを生成する。引数にはbytes-like objectを渡す。
（update関数で渡す方法がリファレンスには書いてある。）

## ハッシュダイジェスト ( = ハッシュ値 )

ハッシュ関数から返される値のこと。  
上記で生成したハッシュオブジェクトからダイジェストを取得するには次の関数を利用する。

* digest()
* hexdigest()

前者は取得したダイジェストをそのまま(0〜255の範囲全てを含み得るバイト列)返すが、hexdigest()は倍の長さの16進形式文字列を返す。これは電子メールなどの非バイナリ環境で値を交換する場合に用いられる。

# 参考

アスキー変換表
http://www3.nit.ac.jp/~tamura/ex2/ascii.html
ISO8859
https://edu.isc.chubu.ac.jp/hsuzuki/iip/2019-katsuyou/w15b/e-learning4.html
Unicode一覧
http://www.unicode.org/charts/
Shift-JIS
https://www.google.com/search?q=shiftJIS&rlz=1C5CHFA_enJP779JP780&oq=shiftJIS&aqs=chrome..69i57j0l7.2823j0j7&sourceid=chrome&ie=UTF-8
base64
https://qiita.com/PlanetMeron/items/2905e2d0aa7fe46a36d4
ハッシュ
https://wa3.i-3-i.info/word15954.html