上传文件
  1. base64hub
  2. 在代码中使用 Base64
  3. Rust
代码

Rust 中的 Base64

Rust 标准库没有 Base64。事实上的标准选择是 base64 crate,它的 API 在 0.21 中改成了 engine 风格。下面的代码片段分别针对 0.23.1 和 0.22.1 构建过。

添加 crate 并编码字符串

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

从 0.21 开始,所有操作都通过 Engine 完成。prelude 模块导出了 Engine trait 和四个现成的 engine:

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 中的 engine:

PreludeEngine字母表编码时的填充解码时的填充
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"<<???>>"

无填充的 engine 会拒绝带填充的输入(BASE64_URL_SAFE_NO_PAD.decode("PDw_Pz8-Pg==") 返回 Err(InvalidPadding))。如果你要接受来自多个生成方的令牌,就构造一个不在乎填充的 engine:

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(带填充 engine 的默认值)和 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()。drop 写入器时虽然也会尝试刷新,但无法报告错误。在一个 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.

字节值是十进制的:95 是 _,61 是 =,10 是换行符。InvalidLastSymbol 表示填充位不为零,这个 crate 默认会拒绝;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)。
  • 忘了导入 trait:没有 use base64::Engine;(或 prelude)时,对 engine 调用 .encode 会报 “no method named encode found” 错误。
  • 空白字符:解码器把换行符和空格视为无效字节。请先去掉它们,例如用 s.split_ascii_whitespace().collect::<String>()。
  • 没有 MIME 辅助函数:这个 crate 不会换行。如果要生成邮件正文,请自己把输出切成 76 个字符一行。

版本说明

  • 0.21:引入 Engine API;自由函数 encode/decode 被废弃,*_config 函数被移除。配置改用 GeneralPurposeConfig 加 DecodePaddingMode。
  • 0.22:DecodeError::InvalidLength 现在指有效符号的数量;decode_slice 可以配合大小恰好的缓冲区使用。
  • 0.23:默认的 simd-unsafe feature 下提供 SIMD engine,新增更多预配置常量、自定义填充符号,MSRV 为 1.71。本页使用的通用 engine 没有变化。

常见问题

为什么 base64::encode 被标记为已废弃?

从 0.21 起,自由函数 base64::encode 和 base64::decode 就成了已废弃的包装函数(0.23 中仍然存在)。请改用 engine:先 use base64::prelude::*;,再调用 BASE64_STANDARD.encode(data)。

为什么 base64 crate 拒绝不带填充的输入?

STANDARD 要求规范的填充。对于不带填充的输入,请使用 URL_SAFE_NO_PAD 或 STANDARD_NO_PAD,或者用 DecodePaddingMode::Indifferent 构造一个两种都接受的 engine。

base64 crate 会跳过换行符吗?

不会。"aGVs\nbG8K" 会以 InvalidByte(4, 10) 失败。解码 MIME 或 PEM 正文之前,请先去掉空白。

继续阅读