ファイルを選択
  1. base64hub
  2. 各言語での Base64
  3. Rust
コード

Rust での Base64

Rust の標準ライブラリには Base64 がありません。事実上の定番は base64 クレートで、0.21 で API がエンジン方式に変わりました。以下のコードは 0.23.1 と 0.22.1 でビルドしました。

クレートを追加して文字列をエンコードする

[dependencies]
base64 = "0.22"   # the snippets below also build with 0.23

0.21 以降、すべての処理は Engine を通して行います。prelude モジュールは Engine トレイトと、すぐに使える 4 つのエンジンをエクスポートしています。

use base64::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let enc = BASE64_STANDARD.encode("héllo wörld");
    println!("{enc}");                              // aMOpbGxvIHfDtnJsZA==

    let bytes = BASE64_STANDARD.decode(&enc)?;
    println!("{}", String::from_utf8(bytes)?);      // héllo wörld
    Ok(())
}

encode は AsRef<[u8]> であれば何でも受け付けるので、&str、String、Vec<u8>、バイトリテラルのどれでも使えます。Rust の文字列は UTF-8 なので、変換の手順は不要です。デコードしたバイト列が有効な UTF-8 でなければ、String::from_utf8 は推測せずにエラーを返します。

prelude の名前と base64::engine::general_purpose の対応:

preludeエンジンアルファベットエンコード時のパディングデコード時のパディング
BASE64_STANDARDSTANDARD+ /あり必須
BASE64_STANDARD_NO_PADSTANDARD_NO_PAD+ /なしあってはならない
BASE64_URL_SAFEURL_SAFE- _あり必須
BASE64_URL_SAFE_NO_PADURL_SAFE_NO_PAD- _なしあってはならない

パディングあり・なしの URLセーフ Base64

let d = b"<<???>>";
BASE64_URL_SAFE.encode(d);          // "PDw_Pz8-Pg=="
BASE64_URL_SAFE_NO_PAD.encode(d);   // "PDw_Pz8-Pg"

let back = BASE64_URL_SAFE_NO_PAD.decode("PDw_Pz8-Pg")?;   // b"<<???>>"

パディングなしのエンジンは、パディング付きの入力を拒否します(BASE64_URL_SAFE_NO_PAD.decode("PDw_Pz8-Pg==") は Err(InvalidPadding) を返します)。複数の生成元からトークンを受け付けるなら、パディングを気にしないエンジンを作ってください。

use base64::alphabet;
use base64::engine::general_purpose::{GeneralPurpose, GeneralPurposeConfig};
use base64::engine::DecodePaddingMode;

const URL_LENIENT: GeneralPurpose = GeneralPurpose::new(
    &alphabet::URL_SAFE,
    GeneralPurposeConfig::new()
        .with_encode_padding(false)
        .with_decode_padding_mode(DecodePaddingMode::Indifferent),
);

URL_LENIENT.decode("PDw_Pz8-Pg").is_ok();     // true
URL_LENIENT.decode("PDw_Pz8-Pg==").is_ok();   // true

ほかのモードは RequireCanonical(パディング付きエンジンのデフォルト)と RequireNone です。バリアントの背景については Base64 と Base64URL の違い と パディングの解説 をご覧ください。

ファイルのエンコードとデコード

let data = std::fs::read("hello.txt")?;
println!("{}", BASE64_STANDARD.encode(&data));          // aGVsbG8K

std::fs::write("copy.txt", BASE64_STANDARD.decode("aGVsbG8K")?)?;

新たなアロケーションなしで既存のバッファに追記するには、encode_string を使います。

let mut s = String::from("Basic ");
BASE64_STANDARD.encode_string(b"user:pass", &mut s);
println!("{s}");                                         // Basic dXNlcjpwYXNz

EncoderWriter と DecoderReader によるストリーム処理

write モジュールと read モジュールは std::io の型をラップするので、大きなファイルがメモリに丸ごと載ることはありません。

use std::fs::File;
use std::io::{self, Read};

let mut input = File::open("big.bin")?;
let mut w = base64::write::EncoderWriter::new(File::create("big.b64")?, &BASE64_STANDARD);
io::copy(&mut input, &mut w)?;
w.finish()?;            // writes the final block and padding

let mut r = base64::read::DecoderReader::new(File::open("big.b64")?, &BASE64_STANDARD);
let mut out = Vec::new();
r.read_to_end(&mut out)?;

finish() は明示的に呼んでください。writer をドロップしたときもフラッシュを試みますが、エラーを報告できません。300 KB のテストファイルでは、ストリーム処理の出力は BASE64_STANDARD.encode と等しく、デコードしたバイト列も元のファイルと一致しました。

検証と DecodeError

decode は Result<Vec<u8>, DecodeError> を返します。以下は BASE64_STANDARD での結果で、0.22.1 の Debug 出力と Display のメッセージを示しています。

入力バリアントメッセージ
PDw_Pz8-Pg==InvalidByte(3, 95)Invalid symbol 95, offset 3.
aGkInvalidPaddingInvalid padding
aGk=!!InvalidByte(3, 61)Invalid symbol 61, offset 3.
aInvalidLength(1)Invalid input length: 1
aGl=InvalidLastSymbol(2, 108)Invalid last symbol 108, offset 2.
"aGVs\nbG8K"InvalidByte(4, 10)Invalid symbol 10, offset 4.

バイト値は 10進数で、95 は _、61 は =、10 は改行です。InvalidLastSymbol は 0 でないパディングビットを意味し、このクレートはデフォルトでこれを拒否します。with_decode_allow_trailing_bits(true) で緩められます。0.23 では InvalidLastSymbol バリアントが変更され、メッセージは Invalid last symbol 0x6c ('l') at offset 2, decoded as 0b00100101. になりました。テキストではなくバリアント名でマッチしてください。

use base64::DecodeError;

match BASE64_STANDARD.decode(input) {
    Ok(bytes) => { /* use bytes */ }
    Err(DecodeError::InvalidByte(offset, byte)) => eprintln!("bad byte {byte} at {offset}"),
    Err(e) => eprintln!("invalid Base64: {e}"),
}

失敗する文字列を Base64 バリデーター に貼り付ければ、問題の文字がわかります。原因は Base64 のエラーの解説 で順に説明しています。

落とし穴

  • 古いチュートリアル。 encode_config(x, base64::URL_SAFE) や config 定数は 0.21 より前のもので、もうコンパイルできません。base64::encode(x) は非推奨の警告付きでまだビルドできます。それぞれ URL_SAFE.encode(x) と STANDARD.encode(x) に書き換えてください。
  • トレイトのインポートを忘れる。 use base64::Engine;(または prelude)がないと、エンジンの .encode 呼び出しは「no method named encode found」で失敗します。
  • 空白。 デコーダーは改行やスペースを不正なバイトとして扱います。たとえば s.split_ascii_whitespace().collect::<String>() で取り除いてください。
  • MIME 用のヘルパーがない。 このクレートは行を折り返しません。メール本文を生成する場合は、出力を自分で 76 文字ずつに分割してください。

バージョンごとの注意

  • 0.21:Engine API が導入。フリー関数の encode/decode は非推奨に、*_config 関数は削除。設定は GeneralPurposeConfig と DecodePaddingMode で行う
  • 0.22:DecodeError::InvalidLength が有効なシンボルの数を指すように変更。decode_slice がちょうどのサイズのバッファで動作するように
  • 0.23:デフォルトの simd-unsafe フィーチャーで SIMD エンジンを追加、事前設定済みの定数が増加、独自のパディング記号に対応、MSRV は 1.71。このページで使った汎用エンジンに変更はなし

よくある質問

base64::encode が非推奨になっているのはなぜですか?

0.21 以降、フリー関数の base64::encode と base64::decode は非推奨のラッパーになりました(0.23 にもまだ残っています)。代わりにエンジンを使ってください:use base64::prelude::*; の後で BASE64_STANDARD.encode(data)。

base64 クレートがパディングのない入力を拒否するのはなぜですか?

STANDARD は正規のパディングを要求します。パディングのない入力には URL_SAFE_NO_PAD か STANDARD_NO_PAD を使うか、DecodePaddingMode::Indifferent でエンジンを作って両方を受け付けるようにしてください。

base64 クレートは改行を読み飛ばしますか?

読み飛ばしません。"aGVs\nbG8K" は InvalidByte(4, 10) で失敗します。MIME や PEM の本文をデコードする前に空白を取り除いてください。

続けて読む