파일 업로드
  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 트레이트와 미리 만들어진 엔진 네 개를 내보냅니다.

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-safe Base64와 패딩 없는 URL-safe 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를 drop해도 flush를 시도하지만 오류를 보고할 수 없습니다. 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)로 바꾸세요.
  • 트레이트 import 누락. 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 함수는 제거. config는 GeneralPurposeConfig와 DecodePaddingMode로 구성.
  • 0.22: DecodeError::InvalidLength가 이제 유효한 기호의 개수를 가리킴. decode_slice가 정확한 크기의 버퍼에서 동작.
  • 0.23: 기본 simd-unsafe 기능 뒤의 SIMD 엔진, 미리 구성된 상수 추가, 사용자 정의 패딩 기호, MSRV 1.71. 이 페이지에서 쓴 범용 엔진은 바뀌지 않았습니다.

자주 묻는 질문

base64::encode가 deprecated로 표시되는 이유는 무엇인가요?

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 본문을 디코딩하기 전에 공백을 제거하세요.

계속 읽기