파일 업로드
  1. base64hub
  2. 코드로 다루는 Base64
  3. Ruby
코드

Ruby에서 Base64 사용하기

Ruby의 Base64 모듈은 Array#pack과 String#unpack1을 얇게 감싼 것입니다. 가장 큰 함정은 strict가 아닌 메서드가 줄 바꿈을 넣는다는 점입니다. 아래 출력은 Ruby 3.4 기준입니다.

문자열 인코딩과 디코딩

MIME 출력이 꼭 필요한 경우가 아니라면 strict_ 메서드를 사용하세요.

require "base64"

enc = Base64.strict_encode64("héllo wörld")
puts enc          # aMOpbGxvIHfDtnJsZA==

dec = Base64.strict_decode64(enc).force_encoding("UTF-8")
puts dec          # héllo wörld

Ruby 문자열은 인코딩 정보를 가지고 있으며, Base64는 바이트의 의미를 모르기 때문에 디코딩한 문자열은 항상 BINARY(ASCII-8BIT)로 돌아옵니다. force_encoding("UTF-8")은 복사 없이 레이블만 바꿉니다. 이를 생략하면 UTF-8 리터럴과의 비교가 실패하고, 문자열 메서드가 각 바이트를 문자 하나로 취급합니다.

원본 문자열이 다른 인코딩(예: 레거시 파일의 Windows-1252나 EUC-KR)이라면 Base64로 인코딩하기 전에 .encode("UTF-8")을 호출하세요. 그렇지 않으면 받는 쪽은 예상과 다른 바이트를 보게 됩니다.

encode64와 strict_encode64

encode64는 MIME 방식입니다. 60자마다 줄 바꿈을 넣고 항상 줄 바꿈으로 끝납니다.

Base64.encode64("hi")          # => "aGk=\n"
Base64.encode64("x" * 60)
# => "eHh4...eHh4\neHh4eHh4eHh4eHh4eHh4\n"   (60 chars per line)
Base64.strict_encode64("hi")   # => "aGk="

이 끝의 \n이 Ruby Base64에서 가장 흔한 버그입니다. HTTP 헤더, 서명, JSON 비교를 망가뜨립니다. Ruby는 60자에서 줄을 바꾸지만, RFC 2045는 최대 76자를 허용하고 다른 대부분의 언어는 76자를 씁니다. 이메일에는 둘 다 유효합니다. 정확히 76자가 필요하다면 MIME Base64 인코더에서 그 형식을 볼 수 있습니다.

패딩 있는 URL-safe Base64와 패딩 없는 URL-safe Base64

Base64.urlsafe_encode64("<<???>>")                  # => "PDw_Pz8-Pg=="
Base64.urlsafe_encode64("<<???>>", padding: false)  # => "PDw_Pz8-Pg"
Base64.urlsafe_decode64("PDw_Pz8-Pg")               # => "<<???>>"

urlsafe_decode64는 패딩이 있든 없든 입력을 받습니다. 빠진 =를 채운 뒤 엄격하게 디코딩합니다. padding: 키워드는 Ruby 2.3부터 있습니다. JWT와 다른 토큰은 Base64와 Base64URL 비교를 참고하거나 Base64URL 디코더로 해 보세요.

검증 없이 JWT의 클레임을 읽는 방법:

require "json"

jwt = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0In0.sig"
header, payload, _sig = jwt.split(".")
JSON.parse(Base64.urlsafe_decode64(header))    # => {"alg" => "HS256"}
JSON.parse(Base64.urlsafe_decode64(payload))   # => {"sub" => "1234"}

{"alg" => "HS256"}라는 출력 형식은 Ruby 3.4의 새로운 Hash#inspect입니다. 이전 버전은 {"alg"=>"HS256"}로 출력합니다.

파일 인코딩과 디코딩

바이너리 모드로 읽고 쓰세요. 그렇지 않으면 Windows의 줄 바꿈 변환과 인코딩 변환 때문에 바이트가 바뀔 수 있습니다.

puts Base64.strict_encode64(File.binread("hello.txt"))   # aGVsbG8K
File.binwrite("copy.txt", Base64.strict_decode64("aGVsbG8K"))

큰 파일을 조각으로 처리하기

Base64 모듈에는 스트리밍 API가 없지만, 각 조각이 3바이트의 배수라면 조각마다 pack("m0")을 써도 됩니다.

File.open("big.bin", "rb") do |src|
  File.open("big.b64", "wb") do |dst|
    while (chunk = src.read(3 * 1024 * 64))   # 192 KiB, divisible by 3
      dst.write([chunk].pack("m0"))
    end
  end
end

300 KB 테스트 파일에서 결과는 strict_encode64(File.binread(...))와 동일했습니다. 다른 조각 크기를 쓰면 출력 중간에 = 패딩이 나타납니다.

pack과 unpack1 직접 사용하기

이 모듈은 편의 계층입니다. 다음은 같은 동작이며 require가 필요 없습니다.

["hi"].pack("m0")    # => "aGk="    (same as strict_encode64)
["hi"].pack("m")     # => "aGk=\n"  (same as encode64)
"aGk=".unpack1("m0") # => "hi"      (same as strict_decode64)

검증과 오류

strict_decode64는 정규 형식의 패딩된 Base64가 아니면 invalid base64라는 메시지와 함께 ArgumentError를 일으킵니다. 여섯 가지 입력을 시험했고 모두 이 오류가 났습니다.

입력문제
PDw_Pz8-Pg==URL-safe 문자
aGk패딩 누락
aGk=!!패딩 뒤의 쓰레기 문자
a불가능한 길이
aGl=0이 아닌 패딩 비트
"aGVs\nbG8K"줄 바꿈
def base64?(s)
  Base64.strict_decode64(s)
  true
rescue ArgumentError
  false
end

strict가 아닌 decode64는 절대 오류를 일으키지 않습니다. 이해하지 못하는 것은 모두 건너뛰므로 오류 대신 쓰레기 값이 나옵니다.

Base64.decode64("aGk=!!")        # => "hi"
Base64.decode64("PDw_Pz8-Pg==")  # => "<<\x0F\xCF\xC3\xE0"   (should be "<<???>>")

decode64는 줄 바꿈이 있는 MIME 본문에만, 그리고 출처를 신뢰할 때만 쓰세요. 문자열이 디코딩되지 않으면 Base64 검사기가 문제를 짚어 주고, 흔한 Base64 오류에서 흔한 원인을 설명합니다.

Ruby 3.4와 Bundler

Ruby 3.4는 base64를 default gem에서 bundled gem으로 옮겼습니다. Bundler 밖에서는 달라지는 것이 없습니다. bundle exec에서는 이 gem이 없는 Gemfile이 실패합니다.

warning: base64 was loaded from the standard library, but is not part of the
default gems starting from Ruby 3.4.0.
...
cannot load such file -- base64 (LoadError)

Gemfile이나 gemspec에 gem "base64"를 추가하거나, 호출을 Ruby 코어에 포함된 pack("m0") / unpack1("m0")으로 바꾸세요.

자주 묻는 질문

Base64.encode64가 문자열에 \n을 붙이는 이유는 무엇인가요?

encode64는 60자마다, 그리고 끝에 줄 바꿈이 있는 RFC 2045 출력을 만듭니다. 헤더, JSON, URL에는 줄 바꿈을 절대 넣지 않는 strict_encode64를 쓰세요.

Ruby 3.4에서 'cannot load such file -- base64' 오류가 나는 이유는 무엇인가요?

Ruby 3.4부터 base64가 default gem이 아니라 bundled gem이 되었기 때문입니다. 일반 ruby로는 여전히 로드되지만, Bundler에서는 Gemfile에 gem "base64"를 추가해야 합니다. 아니면 require를 빼고 [data].pack("m0")을 쓰세요.

Ruby에서 패딩 없는 Base64를 디코딩하려면 어떻게 하나요?

Base64.urlsafe_decode64는 빠진 패딩을 직접 채웁니다. 표준 알파벳이라면 strict_decode64를 호출하기 전에 "=" * (-s.length % 4)를 덧붙이세요.

계속 읽기