Base45 のエンコードとデコードの方法
- 入力 · テキストにテキストを入力するか貼り付けます。Base45 の文字列が出力 · Base45(RFC 9285)に表示されます。
- 圧縮されたバイト列などのバイナリデータの場合は、先に変換元を Hex(16進数)か Base64 にします。
- デコードするには、方向の切り替えで Base45(RFC 9285)→ テキストをクリックし、文字列を貼り付けます。結果がバイナリの場合(健康証明書はそうです)は、変換先で Hex(16進数)か Base64 を選びます。
- 出力をコピーは空白をそのまま保ちます。バイトを保存はデコードしたバイト列をファイルとしてダウンロードします。
Base45 の仕組み
RFC 9285 は入力を 2 バイトずつ処理します。各ペアは 0〜65535 の数 n で、n = c + d × 45 + e × 45² となる 3 桁の 45 進数 c、d、e として、下位の桁から順に書きます。最後に 1 バイトだけ残った場合は 2 桁になります。アルファベットは QR コードの英数字セットを順に並べたものです。
0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:
RFC の例です。どれもこのツールで再現できます。
| 入力 | Base45 |
|---|---|
AB | BB8 |
Hello!! | %69 VD92EX0 |
base-45 | UJCLQE7W581 |
ietf! | QED8WEX0 |
デコード時には、寛容なデコーダーなら見逃しかねない 2 点を確認します。3n + 1 という長さはどんな入力からも生じないこと、そして 65535 を超えるグループ(GGW や ZZZ など)は範囲外であることです。どちらもエラーになります。
Base45 が QR コードに向いている理由
紙の上では Base45 は無駄が多く見えます。2 バイトにつき 3 文字なので 50% の増加で、Base64 の 33% より大きいからです。違いは QR コードがテキストを格納する方法から生まれます。英数字モードでは、QR コードはこの 45 文字のうち 2 文字を 11 ビットに詰めます。したがって Base45 の 3 文字は 16 ビットのデータに対して 16.5 ビットで済み、増加は約 3% です。
Base64 は小文字を必要とするため、英数字モードを使えません。1 文字 8 ビットのバイトモードに入れるしかなく、QR コード内では 33% の増加になります。同じデータなら、Base45 の QR コードのほうが小さくなるか、より多くを格納できます。
EU デジタル COVID 証明書(HC1:)をデコードする
EU デジタル COVID 証明書の QR コードには、HC1: で始まる文字列が入っています。ペイロードは何層にも組み立てられており、このページが扱うのはそのうちの 1 層です。
HC1:の接頭辞を取り除きます。H、C、1、:はどれも有効な Base45 の文字なので、接頭辞を残したままにするとデータとしてデコードされ、その後ろがすべて壊れます。- 残りをここで Base45 デコードします。変換先は Base64 にします。
- 結果を展開します。これは zlib のデータで、バイト
78(Base64 ではeJかeN)で始まります。Base64 を gzip・zlib デコーダーに貼り付けてください。 - CBOR を解析します。展開したバイト列は CBOR の COSE_Sign1 構造で、ヘッダー、証明書のフィールドを含むペイロード、署名からなります。これには CBOR デコーダーが必要ですが、このサイトにはありません。
デコードすれば内容はわかりますが、署名は検証されません。証明書が本物かを確かめるには、発行国の公開鍵と COSE の検証ツールが必要です。
Python での Base45
Python の標準ライブラリには Base45 のモジュールがありません。エンコーダーは RFC をそのまま実装できます。
ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"
def b45encode(data: bytes) -> str:
out = []
for i in range(0, len(data) - 1, 2):
n = data[i] * 256 + data[i + 1]
c, n = n % 45, n // 45
d, e = n % 45, n // 45
out += ALPHABET[c], ALPHABET[d], ALPHABET[e]
if len(data) % 2:
c, d = data[-1] % 45, data[-1] // 45
out += ALPHABET[c], ALPHABET[d]
return "".join(out)
b45encode(b"ietf!") # 'QED8WEX0'
PyPI の base45 パッケージは両方向に対応しています。Python でのバイト列の扱いについては Python のページをご覧ください。
よくある質問
Base45 の出力に空白が含まれるのはなぜですか?
空白は QR コードの英数字セットの 45 文字の 1 つなので、Base45 では値 36 を持つ普通の桁です。Hello!! は %69 VD92EX0 にエンコードされます。コピーするときは空白を残し、フォームやシェルに削られないようにしてください。
ここで EU デジタル COVID 証明書をデコードできますか?
一部はできます。このページが行うのは Base45 の段階です。その後のデータは署名付きの COSE 構造に入った zlib 圧縮の CBOR で、展開の手順と CBOR デコーダーが必要です。手順はこのページで紹介しています。
Base45 を URL に入れても安全ですか?
安全ではありません。空白、%、+、/ がアルファベットに含まれており、どれも URL で意味を持ちます。文字列をパーセントエンコードするか、URL には Base64URL を使ってください。
Base45 の文字列が拒否されるのはなぜですか?
有効な Base45 の長さは 3n 文字か 3n + 2 文字で、3 文字の各グループは 65535 以下でなければなりません。長さが 3n + 1 だったり、GGW のようなグループがあったりするとエラーになります。HC1: の接頭辞が残っているのがよくある原因です。