文字列をBase64にするには
文字列を UTF-8 のバイト列にしてから Base64 に変換し、元の文字列へ戻す方法を各言語で示す。
+ と / を - と _ に置き換える URL セーフ版と、末尾のパディングの有無までを扱う。
公開:
選んだ言語は、ほかのサンプルコードを開いたときも最初に表示する。
Python
import base64
text = "こんにちは!"
encoded = base64.b64encode(text.encode("utf-8")).decode("ascii")
print(encoded) # 44GT44KT44Gr44Gh44GvIQ==
print(base64.b64decode(encoded).decode("utf-8")) # こんにちは!
data = bytes([0xFB, 0xFF])
print(base64.b64encode(data).decode("ascii")) # +/8=
print(base64.urlsafe_b64encode(data).decode("ascii")) # -_8=(+ と / を - と _ に置き換える)b64encode はバイト列を受け取る関数なので、文字列は先に encode("utf-8") でバイト列にする。
結果もバイト列で返るため、文字列として使うなら decode("ascii") で戻す。
+ と / を - と _ に置き換えた URL セーフ版には urlsafe_b64encode を使う。
JavaScript
const text = "こんにちは!";
const encoded = Buffer.from(text, "utf8").toString("base64");
console.log(encoded); // 44GT44KT44Gr44Gh44GvIQ==
console.log(Buffer.from(encoded, "base64").toString("utf8")); // こんにちは!
const data = Buffer.from([0xfb, 0xff]);
console.log(data.toString("base64")); // +/8=
console.log(data.toString("base64url")); // -_8(パディングの = は付かない)
try {
btoa(text); // btoa は Latin-1 の範囲外の文字を受け付けない
} catch (e) {
console.log(e.name); // InvalidCharacterError
}Node.js では Buffer を介して変換し、文字列は Buffer.from(text, "utf8") でバイト列にしてから toString("base64") を呼ぶ。
URL セーフ版は "base64url" を指定でき、こちらは末尾の = が付かない。
ブラウザ由来の btoa は Latin-1 の範囲の文字しか受け付けず、日本語を渡すと InvalidCharacterError になる。
TypeScript
// ブラウザでも Node.js でも動く書き方(Buffer を使わない)
function toBase64(text: string): string {
const bytes = new TextEncoder().encode(text);
let binary = "";
for (const b of bytes) binary += String.fromCharCode(b);
return btoa(binary);
}
function fromBase64(encoded: string): string {
const binary = atob(encoded);
const bytes = Uint8Array.from(binary, (c) => c.charCodeAt(0));
return new TextDecoder().decode(bytes);
}
const encoded: string = toBase64("こんにちは!");
console.log(encoded); // 44GT44KT44Gr44Gh44GvIQ==
console.log(fromBase64(encoded)); // こんにちは!TextEncoder でバイト列にしてから btoa に渡す書き方なら、Buffer が無いブラウザでも Node.js でも同じコードで動く。
btoa は 1 文字が 1 バイトの文字列を要求するため、バイトを String.fromCharCode で 1 文字ずつ文字列に直してから渡す。
戻す側は atob の結果をバイト列に直し、TextDecoder で UTF-8 として読む。
Go
package main
import (
"encoding/base64"
"fmt"
)
func main() {
text := "こんにちは!"
encoded := base64.StdEncoding.EncodeToString([]byte(text))
fmt.Println(encoded) // 44GT44KT44Gr44Gh44GvIQ==
decoded, err := base64.StdEncoding.DecodeString(encoded)
if err != nil {
panic(err)
}
fmt.Println(string(decoded)) // こんにちは!
data := []byte{0xfb, 0xff}
fmt.Println(base64.StdEncoding.EncodeToString(data)) // +/8=
fmt.Println(base64.URLEncoding.EncodeToString(data)) // -_8=
fmt.Println(base64.RawURLEncoding.EncodeToString(data)) // -_8(パディングなし)
}encoding/base64 の StdEncoding が標準の変換で、URL セーフ版は URLEncoding、さらにパディングを付けない版は RawURLEncoding である。
デコードの DecodeString はエラーを返すので、外部から受け取った文字列を戻す場面では err を必ず確認する。
Rust
// Cargo.toml: base64 = "0.22"
use base64::{engine::general_purpose, Engine as _};
fn main() {
let text = "こんにちは!";
let encoded = general_purpose::STANDARD.encode(text.as_bytes());
println!("{}", encoded); // 44GT44KT44Gr44Gh44GvIQ==
let decoded = general_purpose::STANDARD.decode(&encoded).unwrap();
println!("{}", String::from_utf8(decoded).unwrap()); // こんにちは!
let data = [0xfbu8, 0xff];
println!("{}", general_purpose::STANDARD.encode(data)); // +/8=
println!("{}", general_purpose::URL_SAFE.encode(data)); // -_8=
println!("{}", general_purpose::URL_SAFE_NO_PAD.encode(data)); // -_8
}標準ライブラリに Base64 の関数は無いため、base64 クレートを Cargo.toml に追加して使う。
0.22 系では用途ごとの engine(STANDARD・URL_SAFE・URL_SAFE_NO_PAD など)を選び、encode と decode を呼ぶ。
Engine トレイトを use しないと、encode メソッドが見つからないというコンパイルエラー(E0599)になる。
Java
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class Main {
public static void main(String[] args) {
String text = "こんにちは!";
String encoded = Base64.getEncoder().encodeToString(text.getBytes(StandardCharsets.UTF_8));
System.out.println(encoded); // 44GT44KT44Gr44Gh44GvIQ==
byte[] decoded = Base64.getDecoder().decode(encoded);
System.out.println(new String(decoded, StandardCharsets.UTF_8)); // こんにちは!
byte[] data = {(byte) 0xfb, (byte) 0xff};
System.out.println(Base64.getEncoder().encodeToString(data)); // +/8=
System.out.println(Base64.getUrlEncoder().encodeToString(data)); // -_8=
System.out.println(Base64.getUrlEncoder().withoutPadding().encodeToString(data)); // -_8
}
}java.util.Base64 が標準で入っており、getEncoder が標準、getUrlEncoder が URL セーフ版で、withoutPadding を付けるとパディングが消える。
文字列をバイト列にするときは StandardCharsets.UTF_8 を明示し、実行環境の既定の文字コードに結果が左右されないようにする。
C#
using System;
using System.Text;
var text = "こんにちは!";
var encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(text));
Console.WriteLine(encoded); // 44GT44KT44Gr44Gh44GvIQ==
var decoded = Encoding.UTF8.GetString(Convert.FromBase64String(encoded));
Console.WriteLine(decoded); // こんにちは!
var data = new byte[] { 0xfb, 0xff };
var standard = Convert.ToBase64String(data);
Console.WriteLine(standard); // +/8=
// URL セーフ版の専用メソッドは標準ライブラリに無いので、記号を置き換える
var urlSafe = standard.Replace('+', '-').Replace('/', '_').TrimEnd('=');
Console.WriteLine(urlSafe); // -_8Convert.ToBase64String はバイト配列を受け取るので、文字列は先に Encoding.UTF8.GetBytes で変換する。
URL セーフ版の専用の型(System.Buffers.Text.Base64Url)は .NET 8 では使えずコンパイルエラーになるため、+ と / を置き換え、末尾の = を TrimEnd で取り除いて作る。
つまずき
Base64 は文字列ではなくバイト列を変換する仕組みなので、文字列を渡す前にバイト列へ直す必要がある。
Python で文字列のまま b64encode に渡すと「a bytes-like object is required, not 'str'」という TypeError になる。
JavaScript の btoa に「こんにちは!」を渡すと InvalidCharacterError になる。
どちらも、先に UTF-8 のバイト列にしてから渡すと解決する。
文字コードで結果が変わる
同じ「こんにちは!」でも、どの文字コードのバイト列から作るかで Base64 の値は変わる。
UTF-8 から作ると 44GT44KT44Gr44Gh44GvIQ== になり、7 言語のコードは同じ値を返した。
Shift_JIS(cp932)のバイト列から作ると grGC8YLJgr+CzSE= になる。
受け取る側がバイト列を UTF-8 として読む前提なら、送る側も UTF-8 で作る。
URL セーフ版とパディング
標準の Base64 は + と / を使うので、URL やファイル名にそのまま載せると意味を持ってしまう。
URL セーフ版は + を -、/ を _ に置き換える。
バイト列 FB FF は、標準が「+/8=」、URL セーフが「-_8=」、パディングなしが「-_8」になった。
デコードで言語の差が出る。
Python の b64decode は、標準にない - と _ を既定で黙って読み飛ばすため、URL セーフ版の文字列を渡すと「Invalid base64-encoded string」のような分かりにくいエラーになる。
URL セーフ版を戻すときは urlsafe_b64decode を使う。
パディングを省いた文字列は Python では「Incorrect padding」で失敗するが、Node.js の Buffer.from は標準・URL セーフのどちらの文字も、パディングの有無も受け付けた。
暗号化ではない
Base64 は誰でも元に戻せる表記の変換であり、秘密を守る手段にはならない。
3 バイトを 4 文字で表すため、元より約 3 分の 1 長くなる。
「こんにちは!」は UTF-8 で 16 バイトだが、Base64 にすると 24 文字になった。