多くの人がBase64エンコードを使用しますが、Base32を使用する頻度は低く、ZBase32を使用する頻度は低くなります(これについて知っていますか?)。しかし、誰もがアルゴリズムを理解しているわけではありません。 この記事では、これらのエンコーディングの長所と短所について説明し、その実装についても説明します。
少し前までは、httpリンクのアドレスでエンコードされたデータを使用する必要がありました。 ご存じのとおり、http標準は大文字と小文字を区別しないURLを意味し、大文字と小文字を区別するエンコードを使用すると、プロキシサーバーまたはブラウザがデータを損なう可能性があります。
これらの要件を考慮して、アルゴリズムとしてZBase32エンコーディングが選択されました。
判明したように、.baseには(base64とは異なり)標準の実装はないため、自分で作成する必要がありました。 驚いたことに、Base32とZBase32の明確な説明を見つけることが困難になりました。 いくつかの既成のソリューションが見つかりましたが、アルゴリズムを理解し、それらを適用し、大きな数式の魔法を読むことなく、言葉の説明なしにビットシフトを行うことは困難でした。 すべてが私の背後にあるので、私はあなたに基本コーディングの少しの知識を共有したいと思います。 記事は本質的に学術的です。
長所と短所
Base64
AZ、az、0-9、/、+の合計64文字を使用して、一連のバイトで表される情報をエンコードできます。 コード化されたシーケンスの最後には、いくつかの特殊文字(通常は「=」)が含まれる場合があります。
利点:
- 印刷文字で任意のバイトのシーケンスを表すことができます。
- 他のベースエンコーディングと比較すると、結果は133のみです。ソースデータの長さの(3)%。
短所:
- 大文字と小文字を区別するエンコーディング。
Base32
AZ(またはaz)、2〜7の32文字のみを使用します。 エンコードされたシーケンスの最後にいくつかの特殊文字が含まれる場合があります(base64と同様)。
利点:
- 任意のバイトのシーケンスは、印刷可能な文字に変換されます。
- 大文字と小文字を区別しないエンコーディング。
- 文字にあまりにも似ている数字は使用されません(たとえば、0はOに似ていて、1はlに似ています)。
短所:
- コード化されたデータは元のデータの160%です。
Zbase32
エンコードはBase32に似ていますが、次の違いがあります。
- 32文字の人間指向のアルファベット。 コード化された情報の記述、発音、および記憶を容易にする最も精巧な記号表。 著者は、人間にとって最も便利な記号を、最も頻繁に使用される位置に再配置しました。 彼らがどうやってやったのか分かりません。 以下にアルファベットを示します。
- エンコード結果の最後に特殊文字はありません。
Wikipediaの各エンコードの詳細についてはこちらとこちらをご覧ください。ZBase32の実装について直接お話ししたいと思います。
ZBase32エンコードアルゴリズムの説明
より理解を深めるために、アルゴリズムを説明するときにC#で計算を表示できるようにします。
したがって、次の内容の32文字のアルファベットがあります。
static string EncodingTable = "ybndrfg8ejkmcpqxot1uwisza345h769";
入力はバイトの配列(もちろん、それぞれ8ビット)です。これをアルファベットから文字に変換したいと思います。
public static string Encode(byte[] data) {
アルファベットは32個の要素の文字列です。つまり、各文字は0〜31の数字(文字列の文字インデックス)でエンコードされます。 ご存じのように、2進数システムの0〜31の任意の数は、5ビットのバイトを使用して書き込むことができます。 このことから、元のバイトセットをビットの単一配列として表し、それを5ビットの断片に分割すると(下図を参照)、アルファベットから文字の座標セットが取得されます。 実際、それがすべてです。
Base32およびBase64アルゴリズムはZBase32に似ており、異なるアルファベット(Base32の場合は構成、Base64の場合は構成とサイズ)および「ニブル」ビットのサイズ(Base64の場合は6ビット)のみが異なります。
したがって、ソースデータを5ビットの断片に分割する前に、結果が書き込まれる場所を準備することをお勧めします。 静的配列のインデックスについて考えないために、StringBuilderを使用しましょう。
var encodedResult = new StringBuilder((int)Math.Ceiling(data.Length * 8.0 / 5.0));
初期化時に、結果の文字列のサイズをすぐに設定します(アルゴリズムの動作中に拡張する時間を無駄にしないため)。
現在は、元のバイト配列を実行して、5ビットの断片に分割します。 便宜上、これは40バイトであるため、5バイトのグループで作業することをお勧めします。これは、「ピース」の長さの倍数です。 しかし、誰も初期データをカスタマイズしていないことを忘れないでください。そのため、不足の可能性を考慮します。
for (var i = 0; i < data.Length; i += 5) { var byteCount = Math.Min(5, data.Length - i);
5バイトのグループで作業しているため、ビットの連続セットが形成されるバッファーが必要です(合計40ビット)。 ulong型の変数(任意で64ビット)を取得し、現在のバイトのバッチをそこに配置します。
ulong buffer = 0; for (var j = 0; j < byteCount; ++j) { buffer = (buffer << 8) | data[i + j]; }
そして最後の段階は、5ビットの断片と結果の形成から、何が起こったのかを「ゼロにする」ことです。
var bitCount = byteCount * 8; while (bitCount > 0) { var index = bitCount >= 5 ? (int)(buffer >> (bitCount - 5)) & 0x1f : (int)(buffer & (ulong)(0x1f >> (5 - bitCount))) << (5 - bitCount); encodedResult.Append(EncodingTable[index]); bitCount -= 5; }
おそらく、最後のコード例では、一見しただけではすべてがはっきりしているわけではありませんが、少し集中すれば、すべてが適切に配置されます。
デコードプロセスはエンコードプロセスに似ていますが、逆方向のみです。
ZBase32Encoderの完全な実装を確認できます。
おわりに
そして、もちろん、結論として、私は次のことを言いたいです。
4nq7bcgosuemmwcq4gy7ddbcrdeadwcn4napdysttuea6egosmembwfhrdemdwcm4n77bcby4n97bxsozzea9wcn4n67bcby4nhnbwf94n9pbq6oszemxwf74nanhegow8em9wfo4gy7bqgos8emhegos9emyegosmem5wfa4n6pbcgozzemtwfirr