技術情報

RustとPython、JavaScriptでBase64でエンコード・デコードしてみる!

こんにちは、MSKです。
仕事でBase64エンコードを使うことになったので調べました。

Base64とは

Base64はバイナリデータを次に紹介する規則に基づいて文字列に置き換える変換方法です。
64種類の英数字と記号(大文字のAからZ、小文字のaからz、数字の0から9、2種類の記号+と/とパディングとして=)のみを用いて表現されます。

バイナリデータを受け付けることができないシステム(電子メールなど)にバイナリデータを送るために使用されます。

Base64への変換方法

変換方法は以下になります。

  1. 元データを6bitずつに分割します。(最後のデータは6bitに足りなくてもOK)
  2. 最後のデータが6bitになっていない場合、右側に0を追加して6bitにします。
  3. 下の変換表を用いて、4文字ずつ変換します。
  4. 最後に4文字に足りない場合は=を右側に追加します。
  5. 4文字ずつ分割されている文字列たちを連結して1つの文字列にします。
  6. 完成した文字列が変換したデータになります。

変換表

ちょっと見づらいですが、左側のbit列を変換した結果が、その列の右側のbase64の列になります。

bit列Base64bit列Base64
000000A100000g
000001B100001h
000010C100010i
000011D100011j
000100E100100k
000101F100101l
000110G100110m
000111H100111n
001000I101000o
001001J101001p
001010K101010q
001011L101011r
001100M101100s
001101N101101t
001110O101110u
001111P101111v
010000Q110000w
010001R110001x
010010S110010y
010011T110011z
010100U1101000
010101V1101011
010110W1101102
010111X1101113
011000Y1110004
011001Z1110015
011010a1110106
011011b1110117
011100c1111008
011101d1111019
011110e111110+
011111f111111/

いくつかBase64にエンコード・デコードしてみる

試しにいくつかのバイナリや文字列をBase64にエンコードして、デコードしてみます。

バイナリ列のエンコード

まず、バイナリ列からエンコードしてみます。
[0x01,0xA0,0x9B,0x35]というバイト列をエンコードしていきます。
まず上のバイト列を2進数に直します。

0x01 = 0b00000001
0xA0 = 0b10100000
0x9B = 0b10011011
0x35 = 0b00110101

次に上から順番に6bit毎に区切っていきます。
0b000000
0b011010
0b000010
0b011011
0b001101
0b01

最後が4bit足りないので、0で埋めます。
0b000000
0b011010
0b000010
0b011011
0b001101
0b010000

次に変換表から6bitのバイナリデータを4文字ずつ文字に変換します。
0b000000 = A
0b011010 = a
0b000010 = C
0b011011 = b

次が4文字に足りないので、最後に=を2つ追加します。
0b001101 = N
0b010000 = Q
=
=

できた文字を連結します。
AaCbNQ==
これが[0x01,0xA0,0x9B,0x35]をエンコードしたデータになります。

デコードは逆の手順をたどればできるので、省略します。

文字列のエンコード

次に「Hello,World!」という文字列をエンコードしてみます。
まず、Hello,World!という文字列をバイナリに直します。

[0x48,0x65,0x6C,0x6C,0x6F,0x2C,0x57,0x6F,0x72,0x6C,0x64,0x21]となります。
まずは2進数に変換します。
0x48 = 0b01001000
0x65 = 0b01100101
0x6C = 0b01101100
0x6C = 0b01101100
0x6F = 0b01101111
0x2C = 0b00101100
0x57 = 0b01010111
0x6F = 0b01101111
0x72 = 0b01110010
0x6C = 0b01101100
0x64 = 0b01100100
0x21 = 0b00100001

これを同じように6bitごとに分割します。
0b010010
0b000110
0b010101
0b101100
0b011011
0b000110
0b111100
0b101100
0b010101
0b110110
0b111101
0b110010
0b011011
0b000110
0b010000
0b100001

次に変換表から6bitのバイナリデータを4文字ずつ文字に変換します。
0b010010 = S
0b000110 = G
0b010101 = V
0b101100 = s

0b011011 = b
0b000110 = G
0b111100 = 8
0b101100 = s

0b010101 = V
0b110110 = 2
0b111101 = 9
0b110010 = y

0b011011 = b
0b000110 = G
0b010000 = Q
0b100001 = h

できた文字を連結します。
SGVsbG8sV29ybGQh

これが「Hello,World!」をエンコードしたデータになります。

Rustでエンコード・デコード

Rustにはbase64というクレートがあるので、それを使います。
使うためにCargo.tomlに次のように記述します。

[dependencies]
base64="0.13.0"

実際のソースは次のようになります。

use base64::{encode,decode};

fn main() {
    let test_bin:&[u8] = &[0x01,0xA0,0x9B,0x35];
    let encode_bin : String = encode(test_bin);
    println!("{:?} encode {}",test_bin,encode_bin);
    match decode(&encode_bin) {
        Ok(v) => println!("{} decode {:?}",encode_bin,v),
        Err(v) => println!("decode failed:{}",v),
    }

    let test_str : String = "Hello,World!".to_string();
    let encode_str : String = encode(test_str.as_bytes());
    println!("{} encode {}",test_str,encode_str);
    match decode(&encode_str) {
        Ok(v) => println!("{} decode {}",encode_str,v.iter().map(|&s| s as char).collect::<String>()),
        Err(v) => println!("decode failed:{}",v),
    }
}

encodeはバイト列を受け取って、base64にエンコードしてくれます。
デコードの場合はResult型が返されるので、matchで分岐させています。

Pythonでエンコード・デコード

PythonにもBase64を扱うbase64モジュールがあります。

import base64
from encodings import utf_8
test_bin = bytes([0x01,0xA0,0x9B,0x35])
encode_bin = base64.b64encode(test_bin)
print(test_bin.hex(":")," encode ",encode_bin.decode())
decode_bin = base64.b64decode(encode_bin)
print(encode_bin.decode()," decode ",decode_bin.hex(":"))

test_str = "Hello,World!"
encode_str = base64.b64encode(test_str.encode())
print(test_str," encode ",encode_str.decode())
decode_str = base64.b64decode(encode_str)
print(encode_str.decode()," decode ",decode_str.decode())

Rustと同じようにb64encodeでエンコードし、b64decodeでデコードします。

JavaScriptでエンコード・デコード

最後にJavaScriptでBase64エンコード・デコードを行ってみます。

const test_bin = [0x01,0xA0,0x9B,0x35];
let str_test_bin = ""
for(let i =0;i<test_bin.length;i++){
    str_test_bin += String.fromCharCode(test_bin[i]);
}
const encode_bin = btoa(str_test_bin)
console.log(test_bin+" encode "+encode_bin)

const decode_bin = atob(encode_bin);
let decode_bin_array = [];
for(let i =0;i<decode_bin.length;i++){
    decode_bin_array[i] = decode_bin.charCodeAt(i);
}
console.log(encode_bin+" decode "+decode_bin_array);

const test_str = "Hello,World!"
const encode_str = btoa(test_str)
console.log(test_str+" encode "+encode_str)

const decode_str = atob(encode_str);
console.log(encode_str+" decode "+decode_str);

javascriptの場合バイト列をエンコードする場合にめんどくさい処理が必要です。
btoaメソッドでBase64に変換をするのですが、引数がバイナリデータの文字列である必要があります。
String.fromCharCodeにより対象となるバイナリデータを文字コードとする文字列を作り、その値をatobに入れてあげることでBase64にエンコードできます。

最後に

Base64エンコーディングについて調べて、プログラムとして組んでみました。
どのプログラムもある程度簡単に使うことができますね。

以上、「RustとPython、JavaScriptでBase64でエンコード・デコードしてみる!」でした。
最後までご覧頂き、ありがとうございました!

ABOUT ME
MSK
九州在住の組み込み系エンジニアです。 2児の父親でもあります。 数学やプログラミングが趣味です。 最近RustとReact、結び目理論と曲面結び目理論にはまっています。